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 +27 -0
- package/README.md +1 -1
- package/README.zh-CN.md +2 -2
- package/docs/guides/native-drt-authoring.md +1 -0
- package/docs/reference/api-limitations.md +9 -1
- package/install.py +1 -1
- package/package.json +1 -1
- package/resolve-advanced/server/aaf_probe.py +9 -0
- package/resolve-advanced/server/author-interchange.mjs +13 -5
- package/resolve-advanced/server/tools/drt.mjs +1 -1
- package/resolve-advanced/vendor/drp-format/assemble-timeline.js +45 -0
- package/resolve-advanced/vendor/drp-format/place-transition.js +6 -1
- package/src/granular/common.py +1 -1
- package/src/server.py +1 -1
- package/src/utils/api_truth.py +22 -0
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
|
-
[](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.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,
|
|
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.
|
|
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
|
@@ -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
|
-
|
|
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
|
-
|
|
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' | '
|
|
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
|
};
|
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.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
package/src/utils/api_truth.py
CHANGED
|
@@ -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
|
|