davinci-resolve-mcp 2.146.0 → 2.148.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,35 @@
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.148.0 — the audio ceiling doubles: A1–A16
6
+
7
+ ### Changed
8
+
9
+ - **Offline audio placement now reaches A16** (was A8). The r19 media
10
+ template was re-captured live with 16 mono audio tracks — valid Fairlight
11
+ strips included, because a cloned track without a strip renders silent
12
+ (the measured strip law). A9 and A16 placements render-verified on
13
+ Studio 19.1.3.7 at the same -24.1 dB mono-strip level as A1, with the
14
+ gaps digitally silent at the sample level. `drt.assemble` cuts refuse
15
+ at A17 with the re-capture guidance.
16
+
17
+ ## What's New in v2.147.0 — the round-trip QC loop learns markers
18
+
19
+ ### Added
20
+
21
+ - **`editorial.verify_roundtrip` is marker-aware**: timeline markers compare
22
+ through the loop (min-anchored frames within tolerance, names when both
23
+ sides carry them). A re-export with NO markers while the turnover has them
24
+ raises the `markersNotInExport` honesty flag without failing the pass — a
25
+ missing exporter capability is not a conform drift.
26
+
27
+ ### Measured (new Resolve bug, filed in api-limitations)
28
+
29
+ - **`EXPORT_OTIO` drops timeline markers wholesale**: two markers readable
30
+ through the marker API, zero in the exported .otio — while Resolve's own
31
+ OTIO *importer* reads Marker objects fine. Marker-fidelity checks must go
32
+ through the marker API, never an OTIO re-export.
33
+
5
34
  ## What's New in v2.146.0 — bins, and the folder registry law
6
35
 
7
36
  ### 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.146.0-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-2.148.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.146.0-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-2.148.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.146.0 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
15
+ > 本翻译对应 v2.148.0 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
16
16
 
17
17
  一个 Model Context Protocol (MCP) 服务器,让 AI 助手通过官方脚本 API 控制 DaVinci Resolve Studio(达芬奇)。它提供完整的 API 覆盖,外加带护栏的工作流助手,涵盖剪辑、媒体池整理、渲染设置、审阅标记、调色、Fusion、Fairlight、项目生命周期任务、扩展开发,以及不碰源媒体的媒体分析。
18
18
 
@@ -49,7 +49,7 @@ window. `render.verify_output` covers the container-level checks.
49
49
  | Constant retimes, reverse | `cuts[].reverse` | v2.114 |
50
50
  | Freeze frames | `cuts[].freeze` (holds source frame `srcIn`) | v2.134 |
51
51
  | Speed ramps | `cuts[].ramp: [{durationFrames, speed}, …]` (linear segments only) | v2.139 |
52
- | Audio placements, A1–A8 | `cuts[].audioOnly + track` | v2.115 |
52
+ | Audio placements, A1–A16 | `cuts[].audioOnly + track` | v2.115 (A1–A8) · v2.148 (A9–A16) |
53
53
  | Built-in generators | `elements: [{type:'generator', generatorName}]` | v2.110 |
54
54
  | Custom start timecode | `spec.startFrame` / `preserveStartTimecode` | v2.117 |
55
55
  | Timeline markers | `spec.markers` (16 colors, notes, durations, customData) | v2.118 |
@@ -12,7 +12,7 @@ that none exists).
12
12
 
13
13
  **Verified on:** DaVinci Resolve Studio 21.0.2
14
14
 
15
- **Totals:** 35 missing capabilities, 47 bugs / unreliable behaviors.
15
+ **Totals:** 35 missing capabilities, 48 bugs / unreliable behaviors.
16
16
 
17
17
  The authoritative source is the runtime-queryable `api_truth` ledger
18
18
  (`resolve_control api_truth "<query>"`); this document is generated from
@@ -679,3 +679,11 @@ values, or automation-hostile modal prompts.
679
679
  - **Behavior:** Item-level markers (clip locators) are not serialized into the exported .drt at all. They live in the project database as Sm2TiItemLockableBlob rows (same wire codec as timeline markers, BlobOwner = the item's DbId — located by byte search in a live Project.db), and readback via TimelineItem.GetMarkers is fine, but the export omits the blobs even after SaveProject — measured on Studio 19.1.3.7. Asymmetrically, ImportTimelineFromFile ACCEPTS an authored Sm2TiItemLockableBlob and the markers read back perfectly.
680
680
  - **Workaround / current handling:** Do not rely on .drt archives to carry clip markers. To deliver item markers in a .drt, author them offline (drt.assemble cuts[].markers writes the accepted blob); to preserve markers from a live timeline, read them via the marker API and re-author.
681
681
  - **Tags:** timeline, export, drt, markers, silent-failure
682
+
683
+ ### Timeline.Export EXPORT_OTIO (drops timeline markers)
684
+
685
+ - **Object:** `Timeline`
686
+ - **Signature:** `(filePath, EXPORT_OTIO) -> bool`
687
+ - **Behavior:** Timeline markers present and readable through the marker API do not appear in the exported .otio at all — the OTIO Marker schema exists and Resolve's importer reads it, but the exporter writes none (measured on Studio 19.1.3.7: two markers read back at frames 12/72; the export carried zero). Any marker-fidelity QC built on an OTIO re-export silently sees an unmarked timeline.
688
+ - **Workaround / current handling:** Do not use EXPORT_OTIO to carry or verify markers. editorial.verify_roundtrip reports this case as `markersNotInExport` (honesty flag, not a failure); read markers through the marker API for fidelity checks, and author them offline via drt.assemble spec.markers when a .drt must carry them.
689
+ - **Tags:** timeline, export, otio, markers, silent-failure
package/install.py CHANGED
@@ -37,7 +37,7 @@ from src.utils.update_check import (
37
37
 
38
38
  # ─── Version ──────────────────────────────────────────────────────────────────
39
39
 
40
- VERSION = "2.146.0"
40
+ VERSION = "2.148.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.146.0",
3
+ "version": "2.148.0",
4
4
  "description": "NPM bootstrapper for the DaVinci Resolve MCP Server.",
5
5
  "license": "MIT",
6
6
  "author": "Samuel Gursky <samgursky@gmail.com>",
@@ -746,5 +746,36 @@ export function verifyRoundtrip(inputEvents, exportedEvents, opts = {}) {
746
746
  mismatches.push({ kind: 'source-frames', at: i, source: x.source, expectedOffset: srcOffsets[x.source], gotOffset: off });
747
747
  }
748
748
  }
749
- return { pass: mismatches.length === 0, pairs: n, srcOffsets, mismatches };
749
+ // MARKERS (E88): compare track-'MARKER' pseudo-events, min-anchored to
750
+ // each side's own VIDEO record origin like everything else. When the
751
+ // re-export carries NO markers at all while the input has them, that is
752
+ // reported as markersNotInExport rather than failed — several export
753
+ // formats drop markers wholesale, and a missing capability is not a
754
+ // conform drift. When both sides carry markers, they compare strictly.
755
+ const mks = (evts, anchor) => evts
756
+ .filter((e) => String(e.track) === 'MARKER' && e.recIn != null)
757
+ .map((e) => ({ frame: e.recIn - anchor, name: e.name || '' }))
758
+ .sort((x, y) => x.frame - y.frame);
759
+ const anchorOf = (evts) => {
760
+ const v = vids(evts);
761
+ return v.length ? Math.min(...v.map((e) => e.recIn)) : 0;
762
+ };
763
+ const am = mks(inputEvents, anchorOf(inputEvents));
764
+ const bm = mks(exportedEvents, anchorOf(exportedEvents));
765
+ const markers = { input: am.length, exported: bm.length, mismatches: [] };
766
+ let markersNotInExport = false;
767
+ if (am.length && !bm.length) {
768
+ markersNotInExport = true;
769
+ } else {
770
+ if (am.length !== bm.length) markers.mismatches.push({ kind: 'marker-count', input: am.length, exported: bm.length });
771
+ for (let i = 0; i < Math.min(am.length, bm.length); i += 1) {
772
+ if (Math.abs(am[i].frame - bm[i].frame) > recTol) {
773
+ markers.mismatches.push({ kind: 'marker-frame', at: i, input: am[i].frame, exported: bm[i].frame });
774
+ } else if (am[i].name && bm[i].name && am[i].name !== bm[i].name) {
775
+ markers.mismatches.push({ kind: 'marker-name', at: i, input: am[i].name, exported: bm[i].name });
776
+ }
777
+ }
778
+ mismatches.push(...markers.mismatches);
779
+ }
780
+ return { pass: mismatches.length === 0, pairs: n, srcOffsets, mismatches, markers, ...(markersNotInExport ? { markersNotInExport } : {}) };
750
781
  }
@@ -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),markers? (ITEM markers/clip locators: [{frame ITEM-relative, color?, name?, note?, duration?, customData?}] — readback-verified on 19; NOTE Resolve's own EXPORT_DRT drops item markers, this authoring path is the only .drt carrier)}]} | [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' | '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)."),
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-16; presence suppresses the A1 mirror; A1-A8 and the raised A9-A16 ceiling render-verified on 19),markers? (ITEM markers/clip locators: [{frame ITEM-relative, color?, name?, note?, duration?, customData?}] — readback-verified on 19; NOTE Resolve's own EXPORT_DRT drops item markers, this authoring path is the only .drt carrier)}]} | [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' | '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()])
@@ -164,8 +164,10 @@ async function cutSourceIntoClips(drpInput, opts = {}) {
164
164
  // Fairlight model (FLStudioModelBA inside the pool's
165
165
  // Sm2Sequence.FieldsBlob) holds one strip per track, and a cloned
166
166
  // track without a strip imports fine, reads back fine, and renders
167
- // SILENT (measured). The r19 media template is captured with 8
168
- // audio tracks (valid strips included); beyond that, refuse.
167
+ // SILENT (measured). The r19 media template is captured with 16
168
+ // audio tracks (valid strips included; A9/A16 render-verified at
169
+ // the same -24.1 dB as A1's mono-strip law, E89 2026-09-01);
170
+ // beyond that, refuse.
169
171
  throw new Error(
170
172
  `cutSourceIntoClips: audio track ${maxA} exceeds the template's ${tracks.length} ` +
171
173
  'audio tracks — audio tracks cannot be grown offline (no Fairlight strip = silent). ' +
@@ -87,7 +87,7 @@ if not logging.getLogger().handlers:
87
87
  handlers=[logging.StreamHandler()],
88
88
  )
89
89
 
90
- VERSION = "2.146.0"
90
+ VERSION = "2.148.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.146.0"
14
+ VERSION = "2.148.0"
15
15
 
16
16
  import base64
17
17
  import os
@@ -2716,6 +2716,27 @@ API_TRUTH: List[Dict[str, Any]] = [
2716
2716
  "submit": "bug",
2717
2717
  "mitigation": ["drt.assemble cuts[].markers"],
2718
2718
  },
2719
+ {
2720
+ "symbol": "Timeline.Export EXPORT_OTIO (drops timeline markers)",
2721
+ "object": "Timeline",
2722
+ "signature": "(filePath, EXPORT_OTIO) -> bool",
2723
+ "reality": "Timeline markers present and readable through the marker "
2724
+ "API do not appear in the exported .otio at all — the OTIO "
2725
+ "Marker schema exists and Resolve's importer reads it, but "
2726
+ "the exporter writes none (measured on Studio 19.1.3.7: two "
2727
+ "markers read back at frames 12/72; the export carried "
2728
+ "zero). Any marker-fidelity QC built on an OTIO re-export "
2729
+ "silently sees an unmarked timeline.",
2730
+ "recommended": "Do not use EXPORT_OTIO to carry or verify markers. "
2731
+ "editorial.verify_roundtrip reports this case as "
2732
+ "`markersNotInExport` (honesty flag, not a failure); "
2733
+ "read markers through the marker API for fidelity "
2734
+ "checks, and author them offline via drt.assemble "
2735
+ "spec.markers when a .drt must carry them.",
2736
+ "tags": ["timeline", "export", "otio", "markers", "silent-failure"],
2737
+ "submit": "bug",
2738
+ "mitigation": ["editorial.verify_roundtrip markersNotInExport"],
2739
+ },
2719
2740
  ]
2720
2741
 
2721
2742