davinci-resolve-mcp 2.113.0 → 2.114.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/docs/reference/api-limitations.md +1 -1
- package/install.py +1 -1
- package/package.json +1 -1
- package/resolve-advanced/server/author-interchange.mjs +12 -11
- package/resolve-advanced/server/tools/drt.mjs +1 -1
- package/resolve-advanced/vendor/drp-format/assemble-timeline.js +14 -5
- package/resolve-advanced/vendor/drp-format/media-timemap.js +8 -4
- package/src/granular/common.py +1 -1
- package/src/server.py +1 -1
- package/src/utils/api_truth.py +5 -1
|
@@ -529,7 +529,7 @@ values, or automation-hostile modal prompts.
|
|
|
529
529
|
### MediaTimemapBA keyframes are generation-split; 19.x silently ignores the R21 protobuf form
|
|
530
530
|
|
|
531
531
|
- **Object:** `Sm2TimeMap (per-clip retime blob)`
|
|
532
|
-
- **Behavior:** Resolve 21 encodes a retimed clip's KeyframesBA as protobuf points; Resolve 19.1.3 encodes it as a keyed-dict of keyed-dict keyframes ({interp, YOut, YIn, Y, XOut, XIn, X}). On import, 19 SILENTLY IGNORES the protobuf form — the clip reads back and plays at 100% with no warning (measured: identical timelines, one per form; protobuf → source 0..96 over 96 frames, keyed → source 0..48 over 96 frames and a live 50% render). The map spans the WHOLE source stretched by 1/speed; the clip's <In>/<Duration> window into it in RECORD frames (srcIn converts by /speed).
|
|
532
|
+
- **Behavior:** Resolve 21 encodes a retimed clip's KeyframesBA as protobuf points; Resolve 19.1.3 encodes it as a keyed-dict of keyed-dict keyframes ({interp, YOut, YIn, Y, XOut, XIn, X}). On import, 19 SILENTLY IGNORES the protobuf form — the clip reads back and plays at 100% with no warning (measured: identical timelines, one per form; protobuf → source 0..96 over 96 frames, keyed → source 0..48 over 96 frames and a live 50% render). The map spans the WHOLE source stretched by 1/speed; the clip's <In>/<Duration> window into it in RECORD frames (srcIn converts by /speed). REVERSE is the same envelope with the Y endpoints swapped - kf0=(0,YMax), kf1=(XMax,0) - and In then measures from the source END: (frames - srcIn - dur*speed)/speed (measured: a reversed srcIn-24 dur-48 cut reads back source 71->23).
|
|
533
533
|
- **Workaround / current handling:** Author retimes for pre-21 hosts with the keyed form (drt.assemble cuts[].speed does this; encoder byte-exact against a live 19.1.3.7 harvest). Treat any cross-generation timemap as unverified until a readback shows the retimed source range.
|
|
534
534
|
- **Tags:** retime, import, silent-failure, drt
|
|
535
535
|
|
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.114.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
|
@@ -202,9 +202,8 @@ export function eventsToOTIO(events, opts = {}) {
|
|
|
202
202
|
* timeline runs 24fps with origin 86400, so rec/src frames convert as
|
|
203
203
|
* round(frames × 24 / nominalFps). Placement anchors the EARLIEST video
|
|
204
204
|
* event at the origin. Honesty ledger in the returned report: flattened
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
* reason), authored vs
|
|
205
|
+
* zero-speed freezes (constant retimes — forward AND reverse — are AUTHORED
|
|
206
|
+
* as real Sm2TimeMaps, r19 keyed form, readback/render-verified), authored vs
|
|
208
207
|
* dropped transitions (cross-dissolves are AUTHORED when the predecessor
|
|
209
208
|
* abuts the cut and both sides have handle media — render-verified on
|
|
210
209
|
* 19.1.3.7; otherwise dropped with the reason, as a cut at the boundary),
|
|
@@ -258,15 +257,17 @@ export function eventsToAssembleSpec(events, opts = {}) {
|
|
|
258
257
|
const vTrack = trackNum(e.track);
|
|
259
258
|
const cut = { startFrame: recIn, durationFrames, srcIn: toTl(e.srcIn ?? 0, e.fps), ...(vTrack > 1 ? { track: vTrack } : {}) };
|
|
260
259
|
if ((e.speed ?? 100) !== 100 || e.reverse) {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
flattenedRetimes.push({ index: e.index, source: e.source, speed: e.speed, reverse: !!e.reverse, reason: '
|
|
260
|
+
const spd = Math.abs(e.speed ?? 100);
|
|
261
|
+
if (!(spd > 0)) {
|
|
262
|
+
flattenedRetimes.push({ index: e.index, source: e.source, speed: e.speed, reverse: !!e.reverse, reason: 'zero speed (freeze) not supported — played forward at 100%' });
|
|
264
263
|
} else {
|
|
265
|
-
//
|
|
266
|
-
// (r19 keyed form; render
|
|
267
|
-
//
|
|
268
|
-
|
|
269
|
-
|
|
264
|
+
// Constant speed, forward or reverse: authored as a real Sm2TimeMap
|
|
265
|
+
// on the cut (r19 keyed form; readback/render-verified on 19.1.3.7 —
|
|
266
|
+
// reverse reads back source 71→23 for a srcIn-24 dur-48 cut). Audio
|
|
267
|
+
// for retimed cuts is video-only downstream.
|
|
268
|
+
if (spd !== 100) cut.speed = spd / 100;
|
|
269
|
+
if (e.reverse) cut.reverse = true;
|
|
270
|
+
authoredRetimes.push({ index: e.index, source: e.source, speed: spd, ...(e.reverse ? { reverse: true } : {}) });
|
|
270
271
|
}
|
|
271
272
|
}
|
|
272
273
|
if (e.transition) {
|
|
@@ -46,7 +46,7 @@ const assembleSchema = z.object({
|
|
|
46
46
|
spec: z
|
|
47
47
|
.object({})
|
|
48
48
|
.passthrough()
|
|
49
|
-
.describe("assembleTimeline spec: { timelineName?, media?: {mediaFilePath, spec:{width,height,frameCount,fps}, cuts:[{startFrame,durationFrames,srcIn?,track? (1-based video track; >1 = video-only, render-verified stacking),speed? (
|
|
49
|
+
.describe("assembleTimeline spec: { timelineName?, 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)}]} | [same, ...] (multi-source needs media_pool.capture_media_template run once per file), elements?: [{type:'title'|'generator', track, startFrame, durationFrames?, text?, generatorName? ('Solid Color'|'SMPTE Color Bar'|'Grey Scale' render-verified on 19), ...}], transitions? }. startFrame is timeline-absolute (origin 86400)."),
|
|
50
50
|
outputPath: z.string().describe('Absolute path where the importable .drt will be written'),
|
|
51
51
|
targetAppVersion: z
|
|
52
52
|
.union([z.string(), z.number()])
|
|
@@ -114,19 +114,28 @@ async function assembleTimeline(spec = {}) {
|
|
|
114
114
|
sources.forEach((src, i) => {
|
|
115
115
|
(src.cuts || []).forEach((cut) => {
|
|
116
116
|
const out = mediaRefs[i] ? { ...cut, mediaRef: mediaRefs[i] } : { ...cut };
|
|
117
|
-
if (cut.speed !== undefined && cut.speed !== 1) {
|
|
117
|
+
if (cut.reverse || (cut.speed !== undefined && cut.speed !== 1)) {
|
|
118
118
|
// Constant-speed retime (forward only). The Sm2TimeMap spans the
|
|
119
119
|
// whole source stretched by 1/speed; the clip windows into it with
|
|
120
120
|
// RECORD-domain In/Duration (measured live on 19.1.3.7), so the
|
|
121
121
|
// source-domain srcIn converts by /speed here.
|
|
122
|
-
|
|
122
|
+
const speed = cut.speed ?? 1;
|
|
123
|
+
if (!(speed > 0)) throw new RangeError('assembleTimeline: cut.speed must be > 0 (use cut.reverse for backwards)');
|
|
123
124
|
const fps = Math.round(src.spec.fps || 24);
|
|
124
125
|
out.timemap = buildConstantSpeedTimemapKeyed({
|
|
125
|
-
speed
|
|
126
|
-
uniqueId: randomUUID(),
|
|
126
|
+
speed, sourceFrames: src.spec.frameCount, fps,
|
|
127
|
+
uniqueId: randomUUID(), reverse: !!cut.reverse,
|
|
127
128
|
}).toString('hex');
|
|
128
|
-
|
|
129
|
+
// Record-domain In. Forward: srcIn/speed. Reverse: the clip plays
|
|
130
|
+
// source [srcIn, srcIn + dur*speed) BACKWARDS — the map descends
|
|
131
|
+
// from the source tail, so In measures from the END:
|
|
132
|
+
// (sourceFrames - srcIn - dur*speed)/speed (measured live: In=0
|
|
133
|
+
// on a reversed full-speed clip reads back source 191→143).
|
|
134
|
+
out.srcIn = cut.reverse
|
|
135
|
+
? Math.round((src.spec.frameCount - (cut.srcIn ?? 0) - cut.durationFrames * speed) / speed)
|
|
136
|
+
: Math.round((cut.srcIn ?? 0) / speed);
|
|
129
137
|
delete out.speed;
|
|
138
|
+
delete out.reverse;
|
|
130
139
|
}
|
|
131
140
|
allCuts.push(out);
|
|
132
141
|
});
|
|
@@ -227,8 +227,8 @@ function buildConstantSpeedTimemap({ speed, sourceDurationSec, uniqueId, recordD
|
|
|
227
227
|
* @param {string} p.uniqueId - fresh uuid (bare, no braces).
|
|
228
228
|
* @returns {Buffer}
|
|
229
229
|
*/
|
|
230
|
-
function buildConstantSpeedTimemapKeyed({ speed, sourceFrames, fps = 24, uniqueId }) {
|
|
231
|
-
if (!(speed > 0)) throw new RangeError('buildConstantSpeedTimemapKeyed: speed must be > 0 (reverse
|
|
230
|
+
function buildConstantSpeedTimemapKeyed({ speed, sourceFrames, fps = 24, uniqueId, reverse = false }) {
|
|
231
|
+
if (!(speed > 0)) throw new RangeError('buildConstantSpeedTimemapKeyed: speed must be > 0 (pass reverse:true for backwards)');
|
|
232
232
|
if (!Number.isInteger(sourceFrames) || sourceFrames < 1) throw new TypeError('buildConstantSpeedTimemapKeyed: sourceFrames must be a positive integer');
|
|
233
233
|
const YMax = (sourceFrames - 1) / fps;
|
|
234
234
|
const XMax = (sourceFrames / speed - 1) / fps;
|
|
@@ -241,9 +241,13 @@ function buildConstantSpeedTimemapKeyed({ speed, sourceFrames, fps = 24, uniqueI
|
|
|
241
241
|
{ key: 'XIn', type: T_DOUBLE, subType: 0, value: 0 },
|
|
242
242
|
{ key: 'X', type: T_DOUBLE, subType: 0, value: X },
|
|
243
243
|
] }).toString('hex');
|
|
244
|
+
// Reverse (harvested from a live 19.1.3.7 -100% XMEML retime): the SAME
|
|
245
|
+
// envelope with the Y endpoints swapped — kf0=(0, YMax), kf1=(XMax, 0),
|
|
246
|
+
// a descending line. Forward keeps kf1 Y = XMax*speed (harvest: +half-frame
|
|
247
|
+
// convention falls out of the arithmetic, byte-exact either way).
|
|
244
248
|
const keyframes = encodeKeyedDict({ hdr: 1, entries: [
|
|
245
|
-
{ key: '1', type: T_BYTES, subType: 0, value: kf(XMax, XMax * speed) },
|
|
246
|
-
{ key: '0', type: T_BYTES, subType: 0, value: kf(0, 0) },
|
|
249
|
+
{ key: '1', type: T_BYTES, subType: 0, value: reverse ? kf(XMax, 0) : kf(XMax, XMax * speed) },
|
|
250
|
+
{ key: '0', type: T_BYTES, subType: 0, value: reverse ? kf(0, YMax) : kf(0, 0) },
|
|
247
251
|
] }).toString('hex');
|
|
248
252
|
return encodeKeyedDict({ hdr: 1, entries: [
|
|
249
253
|
{ key: 'YMax', type: T_DOUBLE, subType: 0, value: YMax },
|
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.114.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
|
@@ -1927,7 +1927,11 @@ API_TRUTH: List[Dict[str, Any]] = [
|
|
|
1927
1927
|
"source 0..48 over 96 frames and a live 50% render). "
|
|
1928
1928
|
"The map spans the WHOLE source stretched by 1/speed; "
|
|
1929
1929
|
"the clip's <In>/<Duration> window into it in RECORD "
|
|
1930
|
-
"frames (srcIn converts by /speed)."
|
|
1930
|
+
"frames (srcIn converts by /speed). REVERSE is the same "
|
|
1931
|
+
"envelope with the Y endpoints swapped - kf0=(0,YMax), "
|
|
1932
|
+
"kf1=(XMax,0) - and In then measures from the source "
|
|
1933
|
+
"END: (frames - srcIn - dur*speed)/speed (measured: a "
|
|
1934
|
+
"reversed srcIn-24 dur-48 cut reads back source 71->23).",
|
|
1931
1935
|
"recommended": "Author retimes for pre-21 hosts with the keyed "
|
|
1932
1936
|
"form (drt.assemble cuts[].speed does this; encoder "
|
|
1933
1937
|
"byte-exact against a live 19.1.3.7 harvest). Treat "
|