davinci-resolve-mcp 2.166.0 → 2.167.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,30 @@
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.167.0 — E109: flat AAF sound slots keep their lanes; AAF audio cross-fades render
6
+
7
+ An Avid turnover carries dialog, music and effects as SEPARATE flat sound
8
+ MobSlots. The AAF walker labelled every flat slot `A` (only `NestedScope`
9
+ layers were numbered), so a dialog lane and a music bed collided on A1 and
10
+ the bridge refused the whole turnover ("audio events overlap on audio track
11
+ 1 — one track cannot hold both"). The sound `Transition` between the dialog
12
+ clips parsed fine; the lane collapse was the block.
13
+
14
+ ### Fixed
15
+
16
+ - **Flat AAF slots number per media kind in slot order** (`A`, `A2`, `A3` …
17
+ / `V`, `V2` …). The first slot of a kind keeps the bare letter; `NestedScope`
18
+ layers keep their own layer numbering. Render-measured on 19.1.3.7: an AAF
19
+ with a dialog lane (−21 dBFS tone → 24f `MonoAudioDissolve` → −41 dBFS
20
+ tone) over a quiet music bed on its own slot conforms, imports, and renders
21
+ the cross-fade −26.0 → −29.3 → −32.5 → −37.5 → −41.6 dBFS across exactly its
22
+ window, with the second lane audibly present (−41.6 vs −47.1 for one lane).
23
+ - **Channel legs still place once.** Resolve's own AAF export writes one slot
24
+ per audio channel with identical legs; the bridge's merge (and
25
+ `verify_roundtrip`'s dedupe) now key on source/range rather than the lane,
26
+ so channel legs of one clip merge while a different bed on its own lane
27
+ never does.
28
+
5
29
  ## What's New in v2.166.0 — E108: XMEML audio cross-fades conform and render
6
30
 
7
31
  The XMEML walker only looked at `<transitionitem>`s on VIDEO tracks, so an
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.166.0-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-2.167.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.166.0-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-2.167.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.166.0 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
15
+ > 本翻译对应 v2.167.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.166.0"
40
+ VERSION = "2.167.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.166.0",
3
+ "version": "2.167.0",
4
4
  "description": "NPM bootstrapper for the DaVinci Resolve MCP Server.",
5
5
  "license": "MIT",
6
6
  "author": "Samuel Gursky <samgursky@gmail.com>",
@@ -201,8 +201,11 @@ plausible-looking short event list. Three limits are deliberate:
201
201
  - **Effect-only layers produce no events.** A layer wrapping `ScopeReference` (subtitle burns, blends,
202
202
  mattes) applies to what shows through from below and references no media of its own. OTIO materializes
203
203
  such layers as tracks of gaps, so its track count can exceed the number of layers with media.
204
- - **Only `NestedScope` layers are numbered.** Non-nested slots keep the flat `V`/`A` label, which is what
205
- `editorial.mjs`'s `/^A\d*$/` audio-follows-video heuristic reads (`A`, `A2`, all count as audio).
204
+ - **Flat slots number per media kind in slot order** (`A`, `A2`, `A3` … / `V`, `V2` …): an Avid
205
+ turnover carries dialog, music and effects as SEPARATE flat sound MobSlots, and labelling them all
206
+ `A` stacked every bed onto one lane where the bridge refuses the overlap (measured, E109). The first
207
+ slot of a kind keeps the bare letter; `NestedScope` layers keep their own layer numbering. This is
208
+ what `editorial.mjs`'s `/^A\d*$/` audio-follows-video heuristic reads (`A`, `A2`, … all count as audio).
206
209
 
207
210
  ## Provenance & license
208
211
  Vendored libraries are clean offline format-interop and deterministic compute code: no secrets, no
@@ -1248,7 +1248,8 @@ def _walk_slot(segment, *, prefix, fps, state):
1248
1248
 
1249
1249
  A NestedScope slot is a multi-layer track: each layer gets its own numbered label
1250
1250
  (V1..Vn / A1..An) and restarts at record 0, because layers are parallel, not
1251
- sequential. A plain (single-layer) slot keeps the flat "V"/"A" label.
1251
+ sequential. A plain (single-layer) slot keeps the label the caller numbered it
1252
+ with (first of its kind bare "V"/"A", then "A2", "A3" … per slot order — E109).
1252
1253
  """
1253
1254
  if type(segment).__name__ == "NestedScope":
1254
1255
  for n, layer in enumerate(_nested_layers(segment), start=1):
@@ -1456,6 +1457,13 @@ def probe(path):
1456
1457
  # `idx` is monotonic across the WHOLE mob, every slot and every nested layer.
1457
1458
  state = {"idx": 1, "events": [], "unhandled": {}, "effectsWithoutEvents": {}}
1458
1459
  edit_fps = None
1460
+ # FLAT slots number per media kind in slot order (A, A2, A3 … / V, V2 …):
1461
+ # an Avid turnover carries dialog, music and effects as SEPARATE flat
1462
+ # sound MobSlots, and labelling them all "A" stacked every bed onto one
1463
+ # lane where the bridge refuses the overlap (measured, E109). The first
1464
+ # slot of a kind keeps the bare letter (compat with every reader that
1465
+ # matches /^A$/); NestedScope layers keep their own layer numbering.
1466
+ kind_ordinal = {"A": 0, "V": 0}
1459
1467
  for slot in getattr(mob, "slots", []) or []:
1460
1468
  seg = getattr(slot, "segment", None)
1461
1469
  if seg is None:
@@ -1467,7 +1475,11 @@ def probe(path):
1467
1475
  fps = _fps_from_edit_rate(getattr(slot, "edit_rate", None))
1468
1476
  if edit_fps is None:
1469
1477
  edit_fps = fps # picks the timecode slot to trust — see above
1470
- _walk_slot(seg, prefix=_media_kind_to_track(slot), fps=fps, state=state)
1478
+ letter = _media_kind_to_track(slot)
1479
+ kind_ordinal[letter] += 1
1480
+ is_nested = getattr(seg, "class_name", "") == "NestedScope" or type(seg).__name__ == "NestedScope"
1481
+ label = letter if (kind_ordinal[letter] == 1 or is_nested) else f"{letter}{kind_ordinal[letter]}"
1482
+ _walk_slot(seg, prefix=label, fps=fps, state=state)
1471
1483
  events = state["events"]
1472
1484
  sequences.append(
1473
1485
  {
@@ -254,15 +254,19 @@ export function eventsToAssembleSpec(events, opts = {}) {
254
254
  const isMarker = (t) => t === 'MARKER';
255
255
  const vids = events.filter((e) => !isAudio(e.track) && !isMarker(e.track) && e.recIn != null && e.recOut != null);
256
256
  // AAF exports one event per audio CHANNEL — a stereo/dual-mono clip arrives
257
- // as identical A-track legs (measured on a Resolve 19 rich export: every
258
- // audio event duplicated). Merge exact duplicates (same track/source/range)
259
- // so they place once instead of refusing as a same-track overlap.
257
+ // as identical legs, one per channel slot (measured on a Resolve 19 rich
258
+ // export: every audio event duplicated). Merge exact duplicates (same
259
+ // source/range/srcIn) so they place once instead of refusing as a
260
+ // same-track overlap. The key deliberately ignores the LANE: since E109
261
+ // flat slots number A, A2, A3 …, so channel legs of one clip arrive on
262
+ // different lanes — still one clip, placed once — while a different bed on
263
+ // its own lane (dialog A, music A2) keeps its lane and never merges.
260
264
  const audsRaw = events.filter((e) => isAudio(e.track) && e.recIn != null && e.recOut != null);
261
265
  const seenAud = new Set();
262
266
  const auds = [];
263
267
  let audioChannelLegsMerged = 0;
264
268
  for (const e of audsRaw) {
265
- const k = `${e.track}|${e.source}|${e.recIn}|${e.recOut}|${e.srcIn}`;
269
+ const k = `${e.source}|${e.recIn}|${e.recOut}|${e.srcIn}|${e.speed ?? 100}|${e.reverse ? 1 : 0}`;
266
270
  if (seenAud.has(k)) { audioChannelLegsMerged += 1; continue; }
267
271
  seenAud.add(k);
268
272
  auds.push(e);
@@ -976,12 +980,13 @@ export function verifyRoundtrip(inputEvents, exportedEvents, opts = {}) {
976
980
  // AUDIO (E97): compared only when the INPUT declares audio events — a
977
981
  // video-only turnover legitimately re-exports with audio (the A1
978
982
  // convenience mirror), which is reported informationally, never failed.
979
- // AAF channel legs dedupe (same track/source/range arrives once per
980
- // channel); BL/silence legs merge out like video black.
983
+ // AAF channel legs dedupe (the same source/range arrives once per channel
984
+ // slot on numbered lanes since E109, so the key ignores the lane the way
985
+ // the bridge's placement merge does); BL/silence legs merge out like black.
981
986
  const dedupeAud = (list) => {
982
987
  const seen = new Set();
983
988
  return list.filter((e) => {
984
- const k = `${e.track}|${e.source}|${e.recIn}|${e.recOut}|${e.srcIn}`;
989
+ const k = `${e.source}|${e.recIn}|${e.recOut}|${e.srcIn}`;
985
990
  if (seen.has(k)) return false;
986
991
  seen.add(k);
987
992
  return true;
@@ -86,7 +86,7 @@ const markerSchema = z.object({
86
86
  export const editorialTool = {
87
87
  name: 'editorial',
88
88
  description:
89
- 'Editorial integrity (Cluster E) — turnover interchange → normalized events → changelist + conform manifest with TIMING silent-lie guards (flattened retime / dropped J/L-cut audio / framerate-pulldown slip / reverse dropped / transition-handle starvation → flag, skip-not-fake). Report-only (gate: review). Actions: parse_interchange (EDL/OTIO/XMEML natively — incl. Resolve-written FCP7 -1 junction edges (paired in record order) + Solid Color generators, XMEML audio-track transitionitems as audio cross-fades on numbered lanes A/A2/…, and CMX FROM/TO CLIP NAME comments over the generic AX reel — + AAF via pyaaf2 + PRPROJ via gunzip+XML → normalized events incl. span-explicit transitions, BL fade legs, and freezes as zero-speed events (OTIO FreezeFrame, XMEML timeremap 0, PrProj in==out, AAF 0% motion effects); for AAF/PRPROJ pass the file PATH as content; AAF also returns per-sequence startTimecode/startFrame — build the timeline at THAT start, not the Resolve 01:00:00:00 default — and per-clip `geometry` for Avid transform effects), list_sequences (ONE offline picker entry point across xml/edl/otio/drt/drp/aaf/prproj → [{id,name,eventCount}], plus startTimecode/startFrame for AAF), convert_to_interchange (author OTIO/EDL/DRT Resolve CAN import from events or a parsed source; the EDL target writes CMX transition pairs incl. BL fades — the .prproj→Resolve conform bridge, no Premiere needed; editorial timing/transitions survive and per-clip effects/color do not. SPEED/REVERSE survive on the otio (LinearTimeWarp) and edl (M2) targets ONLY — this FLAT drt target flattens every retime to 100% forward and returns `flattened`/`flattenedCount` naming each event that lost one (`flattened` is always present on `drt`, empty when there were none); for a .drt that AUTHORS retimes/dissolves/multi-track/audio, use drt.assemble_from_interchange), turnover_changelist (diff old vs new → moved/retimed/trimmed/replaced/new/gone PLUS the junction diff: transition_added/transition_dropped/transition_changed with fade in/out or dissolve, outgoing/incoming, span and duration/type/pre-roll deltas — zero-length CMX carrier lines and the BL legs that carry fades fold into the junction diff instead of reading as gone/new sources; events pair by closest record position, consumed once, so a source cut twice at two speeds compares instance to instance; + timing flags incl. transition_dropped and dropped_split_audio on any A-track), conform_manifest (per-event assert: source resolved/handles/retime/reverse/TC-base; BL-aware — black legs need no source, fades no black-side handles, and a fade-out tail requirement lands on the picture source), marker_roundtrip (markers with provenance tags), verify_roundtrip (input events vs re-export events -> pass/mismatches + fitted per-source TC offsets + marker compare w/ markersNotInExport honesty flag; FADE-AWARE: BL/Solid-Color legs merge out as blackSegments and fade-window boundary reshapes are excused into fadeReshapedBoundaries instead of failing; RETIME-AWARE: speed/reverse compare pairwise — EXPORT_OTIO carries an authored Sm2TimeMap back as LinearTimeWarp (measured), so a flattened/lost retime fails as drift geometry alone cannot catch; AUDIO-AWARE: declared audio events compare (a video-only export such as EXPORT_EDL flags audioNotInExport instead of failing) (channel legs deduped, mismatches tagged trackType audio) while the mirrored-A1 export of a video-only turnover stays informational; the conform QC loop-closer). Offline (AAF needs pyaaf2; live AAF/DRP import is on the Python davinci-resolve MCP).',
89
+ 'Editorial integrity (Cluster E) — turnover interchange → normalized events → changelist + conform manifest with TIMING silent-lie guards (flattened retime / dropped J/L-cut audio / framerate-pulldown slip / reverse dropped / transition-handle starvation → flag, skip-not-fake). Report-only (gate: review). Actions: parse_interchange (EDL/OTIO/XMEML natively — incl. Resolve-written FCP7 -1 junction edges (paired in record order) + Solid Color generators, XMEML audio-track transitionitems as audio cross-fades on numbered lanes A/A2/…, and CMX FROM/TO CLIP NAME comments over the generic AX reel — + AAF via pyaaf2 (flat sound/picture slots number A/A2/… and V/V2/… in slot order so separate beds keep their lanes; NestedScope layers keep layer numbering) + PRPROJ via gunzip+XML → normalized events incl. span-explicit transitions, BL fade legs, and freezes as zero-speed events (OTIO FreezeFrame, XMEML timeremap 0, PrProj in==out, AAF 0% motion effects); for AAF/PRPROJ pass the file PATH as content; AAF also returns per-sequence startTimecode/startFrame — build the timeline at THAT start, not the Resolve 01:00:00:00 default — and per-clip `geometry` for Avid transform effects), list_sequences (ONE offline picker entry point across xml/edl/otio/drt/drp/aaf/prproj → [{id,name,eventCount}], plus startTimecode/startFrame for AAF), convert_to_interchange (author OTIO/EDL/DRT Resolve CAN import from events or a parsed source; the EDL target writes CMX transition pairs incl. BL fades — the .prproj→Resolve conform bridge, no Premiere needed; editorial timing/transitions survive and per-clip effects/color do not. SPEED/REVERSE survive on the otio (LinearTimeWarp) and edl (M2) targets ONLY — this FLAT drt target flattens every retime to 100% forward and returns `flattened`/`flattenedCount` naming each event that lost one (`flattened` is always present on `drt`, empty when there were none); for a .drt that AUTHORS retimes/dissolves/multi-track/audio, use drt.assemble_from_interchange), turnover_changelist (diff old vs new → moved/retimed/trimmed/replaced/new/gone PLUS the junction diff: transition_added/transition_dropped/transition_changed with fade in/out or dissolve, outgoing/incoming, span and duration/type/pre-roll deltas — zero-length CMX carrier lines and the BL legs that carry fades fold into the junction diff instead of reading as gone/new sources; events pair by closest record position, consumed once, so a source cut twice at two speeds compares instance to instance; + timing flags incl. transition_dropped and dropped_split_audio on any A-track), conform_manifest (per-event assert: source resolved/handles/retime/reverse/TC-base; BL-aware — black legs need no source, fades no black-side handles, and a fade-out tail requirement lands on the picture source), marker_roundtrip (markers with provenance tags), verify_roundtrip (input events vs re-export events -> pass/mismatches + fitted per-source TC offsets + marker compare w/ markersNotInExport honesty flag; FADE-AWARE: BL/Solid-Color legs merge out as blackSegments and fade-window boundary reshapes are excused into fadeReshapedBoundaries instead of failing; RETIME-AWARE: speed/reverse compare pairwise — EXPORT_OTIO carries an authored Sm2TimeMap back as LinearTimeWarp (measured), so a flattened/lost retime fails as drift geometry alone cannot catch; AUDIO-AWARE: declared audio events compare (a video-only export such as EXPORT_EDL flags audioNotInExport instead of failing) (channel legs deduped, mismatches tagged trackType audio) while the mirrored-A1 export of a video-only turnover stays informational; the conform QC loop-closer). Offline (AAF needs pyaaf2; live AAF/DRP import is on the Python davinci-resolve MCP).',
90
90
  async handler({ action, args }) {
91
91
  if (action === 'parse_interchange') {
92
92
  const p = parseSchema.parse(args);
@@ -87,7 +87,7 @@ if not logging.getLogger().handlers:
87
87
  handlers=[logging.StreamHandler()],
88
88
  )
89
89
 
90
- VERSION = "2.166.0"
90
+ VERSION = "2.167.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.166.0"
14
+ VERSION = "2.167.0"
15
15
 
16
16
  import base64
17
17
  import os