davinci-resolve-mcp 2.142.1 → 2.143.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,33 @@
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.143.0 — item markers, and two refinements
6
+
7
+ ### Added
8
+
9
+ - **Item-level markers (clip locators) authored offline** — `cuts[].markers`
10
+ (frames item-relative), on video AND explicit-audio items. Found by a raw
11
+ byte-hunt in a live Project.db: item markers ride an
12
+ `Sm2TiItemLockableBlob` in `project.xml`'s LocableBlobSet — the same wire
13
+ codec as timeline markers with the clip's DbId as owner. The kicker, filed
14
+ as a new Resolve bug in api-limitations: **Resolve's own EXPORT_DRT drops
15
+ item-marker blobs entirely** (even after SaveProject), while
16
+ `ImportTimelineFromFile` accepts an authored one — this authoring path is
17
+ the only way a .drt carries clip markers. Live-verified readback on video
18
+ and A3 audio items through the tool layer.
19
+ - **AAF `CutPoint` honored** — when an AAF transition names where the
20
+ notional cut sits within the overlap, the boundary shift lands there
21
+ (clamped strictly inside the span per the edge law) instead of centering.
22
+
23
+ ### Changed
24
+
25
+ - **`fade-to-color` demoted from the transition style set** — on the
26
+ dissolve skeleton it is duration/direction-erratic (a 24f junction
27
+ rendered a ~77 plateau; a 48f one rendered a single black frame then a
28
+ hard cut), and its real parameter blob is unharvestable while the XMEML
29
+ importer stays inert. Turnover Fade-To-Colors now map to `dip`, which is
30
+ duration-stable (clean symmetric valley measured at both 24f and 48f).
31
+
5
32
  ## What's New in v2.142.1 — the AAF leg joins the span fix
6
33
 
7
34
  ### Fixed
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.142.1-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-2.143.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.142.1-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-2.143.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.142.1 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
15
+ > 本翻译对应 v2.143.0 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
16
16
 
17
17
  一个 Model Context Protocol (MCP) 服务器,让 AI 助手通过官方脚本 API 控制 DaVinci Resolve Studio(达芬奇)。它提供完整的 API 覆盖,外加带护栏的工作流助手,涵盖剪辑、媒体池整理、渲染设置、审阅标记、调色、Fusion、Fairlight、项目生命周期任务、扩展开发,以及不碰源媒体的媒体分析。
18
18
 
@@ -53,6 +53,7 @@ window. `render.verify_output` covers the container-level checks.
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 |
56
+ | Item markers (clip locators) | `cuts[].markers` (frames item-relative; video + audio items) | v2.143 |
56
57
  | Turnover markers | EDL `* LOC:` locators + OTIO markers → authored | v2.119 |
57
58
  | TC-bearing sources (AAF route) | native clip capture (`MediaStartTime`), channel-leg merge | v2.120–2.122 |
58
59
  | Subtitles | `spec.subtitles` / `spec.subtitlesSrt` (raw SRT) | v2.128 |
@@ -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, 46 bugs / unreliable behaviors.
15
+ **Totals:** 35 missing capabilities, 47 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
@@ -671,3 +671,11 @@ values, or automation-hostile modal prompts.
671
671
  - **Behavior:** Video <transitionitem>s imported from an FCP7 XMEML land as real Sm2TiTransition elements that READ BACK through the item APIs but render INERT: the outgoing clip plays through the transition window and hard-cuts at its end. Measured on Studio 19.1.3.7 with both a plain Cross Dissolve and a Dip to Color Dissolve (midpoint frames byte-matched the outgoing clip; no blend, no dip). The same transition elements authored offline with a correct FieldsBlob render perfectly, and EDL-imported dissolves/wipes also render — the defect is specific to the XMEML import path's element construction.
672
672
  - **Workaround / current handling:** Do not trust an XMEML-imported timeline's transitions without a render probe at a junction midpoint. To conform an XMEML turnover with working transitions, route it through drt.assemble_from_interchange (format 'xml'), which authors render-verified dissolves, wipes, and the dissolve-family styles from the same <transitionitem> data.
673
673
  - **Tags:** timeline, import, xmeml, transition, readback, silent-failure
674
+
675
+ ### Timeline.Export EXPORT_DRT (drops timeline-item markers)
676
+
677
+ - **Object:** `Timeline`
678
+ - **Signature:** `(filePath, EXPORT_DRT) -> bool`
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
+ - **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
+ - **Tags:** timeline, export, drt, 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.142.1"
40
+ VERSION = "2.143.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.142.1",
3
+ "version": "2.143.0",
4
4
  "description": "NPM bootstrapper for the DaVinci Resolve MCP Server.",
5
5
  "license": "MIT",
6
6
  "author": "Samuel Gursky <samgursky@gmail.com>",
@@ -1175,6 +1175,15 @@ def _walk_components(sequence, *, track, fps, rec, state, depth, transition=None
1175
1175
  # exactly the AAF CutPoint default (centered in the overlap). The old
1176
1176
  # centered-on-recIn placement put HALF the blend before the overlap began.
1177
1177
  pending_transition = {"type": "dissolve", "duration": duration, "alignment": "start"}
1178
+ # CutPoint (optional): where the notional cut sits WITHIN the
1179
+ # overlap. The bridge shifts the reshaped clip boundary there
1180
+ # instead of the centered default, preserving Avid's intent.
1181
+ try:
1182
+ cut_point = comp["CutPoint"].value
1183
+ if cut_point is not None:
1184
+ pending_transition["cutPoint"] = int(cut_point)
1185
+ except (KeyError, AttributeError, TypeError, ValueError):
1186
+ pass
1178
1187
  # Rewind: the next component overlaps the previous one by `duration`.
1179
1188
  # Clamped at this sequence's own start — a leading transition has no
1180
1189
  # preceding material to overlap, and a negative record position would
@@ -323,7 +323,7 @@ export function eventsToAssembleSpec(events, opts = {}) {
323
323
  atFrame: recIn, durationFrames: d, track: vTrack, pre,
324
324
  explicitSpan: e.transition.alignment != null || e.transition.inOffset != null || e.transition.recStart != null,
325
325
  index: e.index, type: e.transition.type, rawDuration: e.transition.duration,
326
- source: e.source, srcIn: cut.srcIn, incomingCutRef: cut,
326
+ source: e.source, srcIn: cut.srcIn, incomingCutRef: cut, cutPoint: e.transition.cutPoint,
327
327
  });
328
328
  }
329
329
  placements.push({ start: recIn, end: recOut, index: e.index, source: e.source, srcIn: cut.srcIn, durationFrames, track: vTrack, cutRef: cut });
@@ -360,7 +360,7 @@ export function eventsToAssembleSpec(events, opts = {}) {
360
360
  atFrame: recIn, durationFrames: d, track, pre,
361
361
  explicitSpan: e.transition.alignment != null || e.transition.inOffset != null,
362
362
  index: e.index, type: e.transition.type, rawDuration: e.transition.duration,
363
- source: e.source, srcIn: cut.srcIn, incomingCutRef: cut,
363
+ source: e.source, srcIn: cut.srcIn, incomingCutRef: cut, cutPoint: e.transition.cutPoint,
364
364
  });
365
365
  }
366
366
  audioPlacements.push({ start: recIn, end: recOut, index: e.index, track, source: e.source, srcIn: cut.srcIn, durationFrames, cutRef: cut });
@@ -457,7 +457,7 @@ export function eventsToAssembleSpec(events, opts = {}) {
457
457
  else if (/dip to color/i.test(raw)) kind = 'dip';
458
458
  else if (/non-additive/i.test(raw)) kind = 'non-additive';
459
459
  else if (/additive/i.test(raw)) kind = 'additive';
460
- else if (/fade to color/i.test(raw)) kind = 'fade-to-color';
460
+ else if (/fade to color/i.test(raw)) kind = 'dip'; // fade-to-color is erratic on the skeleton (measured); dip is the verified fade-to-black-and-back
461
461
  else if (/smooth cut/i.test(raw)) kind = 'smooth-cut';
462
462
  // EDGE LAW (E73, measured): the clip boundary must sit STRICTLY INSIDE
463
463
  // the transition span — an edge-aligned span (Start == boundary) renders
@@ -468,7 +468,11 @@ export function eventsToAssembleSpec(events, opts = {}) {
468
468
  let atFrame = c.atFrame;
469
469
  const spanStartAbs = c.atFrame - pre;
470
470
  if (c.explicitSpan && (pre === 0 || pre === c.durationFrames)) {
471
- const shift = (pre === 0 ? 1 : -1) * (c.durationFrames / 2);
471
+ // AAF CutPoint: the notional cut's offset within the overlap shift
472
+ // the reshaped boundary there when given (clamped strictly inside the
473
+ // span, the edge law), else center.
474
+ const cp = Number.isInteger(c.cutPoint) ? Math.min(c.durationFrames - 1, Math.max(1, c.cutPoint)) : c.durationFrames / 2;
475
+ const shift = (pre === 0 ? 1 : -1) * cp;
472
476
  prev.cutRef.durationFrames += shift;
473
477
  c.incomingCutRef.startFrame += shift;
474
478
  c.incomingCutRef.srcIn += shift;
@@ -507,7 +511,11 @@ export function eventsToAssembleSpec(events, opts = {}) {
507
511
  let atFrame = c.atFrame;
508
512
  const spanStartAbs = c.atFrame - pre;
509
513
  if (c.explicitSpan && (pre === 0 || pre === c.durationFrames)) {
510
- const shift = (pre === 0 ? 1 : -1) * (c.durationFrames / 2);
514
+ // AAF CutPoint: the notional cut's offset within the overlap shift
515
+ // the reshaped boundary there when given (clamped strictly inside the
516
+ // span, the edge law), else center.
517
+ const cp = Number.isInteger(c.cutPoint) ? Math.min(c.durationFrames - 1, Math.max(1, c.cutPoint)) : c.durationFrames / 2;
518
+ const shift = (pre === 0 ? 1 : -1) * cp;
511
519
  prev.cutRef.durationFrames += shift;
512
520
  c.incomingCutRef.startFrame += shift;
513
521
  c.incomingCutRef.srcIn += shift;
@@ -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)}]} | [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' | 'fade-to-color' | '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-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)."),
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()])
@@ -209,6 +209,51 @@ async function assembleTimeline(spec = {}) {
209
209
  const { buffer: baseBuffer, timelineName: tlName, startFrame } = base;
210
210
  let buffer = baseBuffer;
211
211
 
212
+ // ITEM-level markers (clip locators): cuts[].markers, frames ITEM-relative.
213
+ // They live in project.xml's LocableBlobSet as Sm2TiItemLockableBlob —
214
+ // same wire codec as timeline markers, BlobOwner = the CLIP's DbId
215
+ // (measured E78 via a live DB byte-hunt: the .drt EXPORTER drops these
216
+ // blobs entirely, but the IMPORTER accepts an authored one — frames,
217
+ // colors, notes, durations and customData all read back through the
218
+ // marker API). Placed right after cuts, while the parent container is
219
+ // still the only one (compounds haven't run).
220
+ {
221
+ const itemMarkerCuts = [];
222
+ (Array.isArray(media) ? media : media ? [media] : []).forEach((src) => {
223
+ (src.cuts || []).forEach((cut) => {
224
+ if (Array.isArray(cut.markers) && cut.markers.length) itemMarkerCuts.push(cut);
225
+ });
226
+ });
227
+ if (itemMarkerCuts.length) {
228
+ const { getTrackVec } = require('./seq-surgery');
229
+ const zipIM = await JSZip.loadAsync(buffer);
230
+ const seqName = Object.keys(zipIM.files).find((n) => !zipIM.files[n].dir && /SeqContainer\/.+\.xml$/.test(n));
231
+ const seqXml = await zipIM.file(seqName).async('string');
232
+ let pj = await zipIM.file('project.xml').async('string');
233
+ if (!/<LocableBlobSet>[\s\S]*?<\/LocableBlobSet>/.test(pj)) throw new Error('assembleTimeline: project.xml has no LocableBlobSet for item markers');
234
+ for (const cut of itemMarkerCuts) {
235
+ for (const m of cut.markers) {
236
+ if (!Number.isInteger(m.frame) || m.frame < 0 || m.frame >= cut.durationFrames) {
237
+ throw new RangeError(`assembleTimeline: item marker frame ${m.frame} outside the cut (frames are ITEM-relative, [0, ${cut.durationFrames}))`);
238
+ }
239
+ }
240
+ const kind = cut.audioOnly ? 'audio' : 'video';
241
+ const { tracks } = getTrackVec(seqXml, kind);
242
+ const trackXml = tracks[(cut.track ?? 1) - 1];
243
+ if (!trackXml) throw new Error(`assembleTimeline: item markers — track ${cut.track ?? 1} not found`);
244
+ const tag = cut.audioOnly ? 'Sm2TiAudioClip' : 'Sm2TiVideoClip';
245
+ const re = new RegExp(`<${tag} DbId="([^"]+)">(?:(?!<\\/${tag}>)[\\s\\S])*?<Start>${cut.startFrame}<\\/Start>`);
246
+ const hit = trackXml.match(re);
247
+ if (!hit) throw new Error(`assembleTimeline: item markers — no ${kind} clip starting at ${cut.startFrame} on track ${cut.track ?? 1}`);
248
+ const blob = encodeTimelineMarkersBlob(cut.markers);
249
+ const el = `<Element>\n <Sm2TiItemLockableBlob DbId="${randomUUID()}">\n <FieldsBlob>${blob.toString('hex')}</FieldsBlob>\n <BlobOwner>${hit[1]}</BlobOwner>\n <DbSavedTime>0</DbSavedTime>\n </Sm2TiItemLockableBlob>\n </Element>\n `;
250
+ pj = pj.replace('</LocableBlobSet>', `${el}</LocableBlobSet>`);
251
+ }
252
+ zipIM.file('project.xml', pj);
253
+ buffer = await zipIM.generateAsync({ type: 'nodebuffer', compression: 'DEFLATE' });
254
+ }
255
+ }
256
+
212
257
  for (const [i, el] of elements.entries()) {
213
258
  if (!el || typeof el !== 'object') throw new TypeError(`assembleTimeline: elements[${i}] must be an object`);
214
259
  if (el.type === 'title') {
@@ -75,10 +75,15 @@ const WIPE_FIELDS_BLOB = '00000002000000158012120000002c789c636660642016000000da
75
75
  // renders INERT — so authoring through this path beats the host importer.
76
76
  // Unknown PrettyTypes are refused, not probed: unvetted strings are exactly
77
77
  // the class that crashed the app elsewhere (interp!=0, dangling ids).
78
+ // 'Fade To Color' is deliberately ABSENT: on the dissolve skeleton its
79
+ // behavior is duration/direction-erratic (24f rendered a ~77 plateau, 48f a
80
+ // single black frame then a hard cut — measured E68/E75; its real params
81
+ // live in an EffectFiltersBA no importer path can harvest, the XMEML
82
+ // importer being inert). Dip is the verified equivalent (fade to black and
83
+ // back, clean symmetric valley at 24f AND 48f).
78
84
  const STYLE_PRETTY_TYPES = {
79
85
  dip: 'Dip To Color Dissolve',
80
86
  additive: 'Additive Dissolve',
81
- 'fade-to-color': 'Fade To Color',
82
87
  'smooth-cut': 'Smooth Cut',
83
88
  'non-additive': 'Non-Additive Dissolve',
84
89
  };
@@ -87,7 +87,7 @@ if not logging.getLogger().handlers:
87
87
  handlers=[logging.StreamHandler()],
88
88
  )
89
89
 
90
- VERSION = "2.142.1"
90
+ VERSION = "2.143.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.142.1"
14
+ VERSION = "2.143.0"
15
15
 
16
16
  import base64
17
17
  import os
@@ -2694,6 +2694,28 @@ API_TRUTH: List[Dict[str, Any]] = [
2694
2694
  "submit": "bug",
2695
2695
  "mitigation": ["drt.assemble_from_interchange"],
2696
2696
  },
2697
+ {
2698
+ "symbol": "Timeline.Export EXPORT_DRT (drops timeline-item markers)",
2699
+ "object": "Timeline",
2700
+ "signature": "(filePath, EXPORT_DRT) -> bool",
2701
+ "reality": "Item-level markers (clip locators) are not serialized into "
2702
+ "the exported .drt at all. They live in the project database "
2703
+ "as Sm2TiItemLockableBlob rows (same wire codec as timeline "
2704
+ "markers, BlobOwner = the item's DbId — located by byte "
2705
+ "search in a live Project.db), and readback via "
2706
+ "TimelineItem.GetMarkers is fine, but the export omits the "
2707
+ "blobs even after SaveProject — measured on Studio 19.1.3.7. "
2708
+ "Asymmetrically, ImportTimelineFromFile ACCEPTS an authored "
2709
+ "Sm2TiItemLockableBlob and the markers read back perfectly.",
2710
+ "recommended": "Do not rely on .drt archives to carry clip markers. To "
2711
+ "deliver item markers in a .drt, author them offline "
2712
+ "(drt.assemble cuts[].markers writes the accepted blob); "
2713
+ "to preserve markers from a live timeline, read them via "
2714
+ "the marker API and re-author.",
2715
+ "tags": ["timeline", "export", "drt", "markers", "silent-failure"],
2716
+ "submit": "bug",
2717
+ "mitigation": ["drt.assemble cuts[].markers"],
2718
+ },
2697
2719
  ]
2698
2720
 
2699
2721