davinci-resolve-mcp 2.118.0 → 2.119.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.
|
@@ -49,6 +49,7 @@ window. `render.verify_output` covers the container-level checks.
|
|
|
49
49
|
| Built-in generators | `elements: [{type:'generator', generatorName}]` | v2.110 |
|
|
50
50
|
| Custom start timecode | `spec.startFrame` / `preserveStartTimecode` | v2.117 |
|
|
51
51
|
| Timeline markers | `spec.markers` (16 colors, notes, durations, customData) | v2.118 |
|
|
52
|
+
| Turnover markers | EDL `* LOC:` locators + OTIO markers → authored | v2.119 |
|
|
52
53
|
| Fusion titles | `elements: [{type:'title', text}]` — **21-gen hosts only** | v2.108 |
|
|
53
54
|
|
|
54
55
|
`assemble_from_interchange` drives the same engine from an EDL / OTIO /
|
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.119.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
|
@@ -229,9 +229,11 @@ export function eventsToAssembleSpec(events, opts = {}) {
|
|
|
229
229
|
}
|
|
230
230
|
const DEFAULT_ORIGIN = 86400;
|
|
231
231
|
const isAudio = (t) => /^A\d*$/.test(String(t || ''));
|
|
232
|
-
const
|
|
232
|
+
const isMarker = (t) => t === 'MARKER';
|
|
233
|
+
const vids = events.filter((e) => !isAudio(e.track) && !isMarker(e.track) && e.recIn != null && e.recOut != null);
|
|
233
234
|
const auds = events.filter((e) => isAudio(e.track) && e.recIn != null && e.recOut != null);
|
|
234
|
-
const
|
|
235
|
+
const markerEvents = events.filter((e) => isMarker(e.track) && e.recIn != null);
|
|
236
|
+
const audioSkipped = events.length - vids.length - auds.length - markerEvents.length;
|
|
235
237
|
if (!vids.length) throw new Error('eventsToAssembleSpec: no video events with record ranges');
|
|
236
238
|
|
|
237
239
|
const unmapped = [...new Set([...vids, ...auds].map((e) => e.source).filter((srcName) => !sourceMap[srcName]))];
|
|
@@ -426,12 +428,31 @@ export function eventsToAssembleSpec(events, opts = {}) {
|
|
|
426
428
|
transitions.push({ track: c.track, atFrame: c.atFrame, durationFrames: c.durationFrames, trackType: 'audio' });
|
|
427
429
|
}
|
|
428
430
|
|
|
431
|
+
// Turnover markers (EDL * LOC: locators, OTIO Marker objects) → authored
|
|
432
|
+
// timeline markers. Interchange colors map to the measured Resolve names;
|
|
433
|
+
// unknown colors fall back to Blue. Frames are timeline-absolute like cuts.
|
|
434
|
+
const COLOR_MAP = {
|
|
435
|
+
blue: 'Blue', cyan: 'Cyan', green: 'Green', yellow: 'Yellow', red: 'Red',
|
|
436
|
+
pink: 'Pink', purple: 'Purple', magenta: 'Fuchsia', fuchsia: 'Fuchsia',
|
|
437
|
+
rose: 'Rose', lavender: 'Lavender', sky: 'Sky', mint: 'Mint',
|
|
438
|
+
lemon: 'Lemon', sand: 'Sand', cocoa: 'Cocoa', cream: 'Cream',
|
|
439
|
+
orange: 'Sand', white: 'Cream', black: 'Cocoa',
|
|
440
|
+
};
|
|
441
|
+
const markers = markerEvents
|
|
442
|
+
.map((e) => ({
|
|
443
|
+
frame: ORIGIN + (toTl(e.recIn, e.fps) - minRec),
|
|
444
|
+
color: COLOR_MAP[String(e.color || '').toLowerCase()] || 'Blue',
|
|
445
|
+
...(e.name ? { name: e.name } : {}),
|
|
446
|
+
}))
|
|
447
|
+
.filter((m) => m.frame >= (preserveStartTimecode ? startFrame : DEFAULT_ORIGIN));
|
|
448
|
+
|
|
429
449
|
return {
|
|
430
|
-
spec: { timelineName, media, ...(preserveStartTimecode ? { startFrame } : {}), ...(transitions.length ? { transitions } : {}) },
|
|
450
|
+
spec: { timelineName, media, ...(preserveStartTimecode ? { startFrame } : {}), ...(markers.length ? { markers } : {}), ...(transitions.length ? { transitions } : {}) },
|
|
431
451
|
report: {
|
|
432
452
|
videoEvents: vids.length,
|
|
433
453
|
sources: media.length,
|
|
434
454
|
audioEventsSkipped: audioSkipped,
|
|
455
|
+
authoredMarkers: markers.length,
|
|
435
456
|
authoredAudioEvents: audioPlacements.length,
|
|
436
457
|
audioRetimesSkipped,
|
|
437
458
|
upperTrackCutsVideoOnly: placements.filter((pl) => pl.track > 1).length,
|
|
@@ -49,6 +49,8 @@ function evt(o) {
|
|
|
49
49
|
return {
|
|
50
50
|
index: o.index ?? null,
|
|
51
51
|
track: o.track || 'V',
|
|
52
|
+
...(o.name !== undefined ? { name: o.name } : {}),
|
|
53
|
+
...(o.color !== undefined ? { color: o.color } : {}),
|
|
52
54
|
source: o.source || 'UNKNOWN',
|
|
53
55
|
srcIn: o.srcIn ?? null,
|
|
54
56
|
srcOut: o.srcOut ?? null,
|
|
@@ -83,6 +85,19 @@ export function parseEDL(text, opts = {}) {
|
|
|
83
85
|
}
|
|
84
86
|
continue;
|
|
85
87
|
}
|
|
88
|
+
// Avid-style locators: `* LOC: 01:00:01:12 BLUE marker text` — a marker
|
|
89
|
+
// at an absolute record timecode. Emitted as track 'MARKER' pseudo-events
|
|
90
|
+
// (recIn only) so the assemble bridge can author them; consumers that
|
|
91
|
+
// filter video/audio by track shape ignore them.
|
|
92
|
+
const loc = /^\*\s*LOC:\s*(\d{2}:\d{2}:\d{2}[:;]\d{2})\s+(\S+)\s*(.*)$/i.exec(line);
|
|
93
|
+
if (loc) {
|
|
94
|
+
events.push(evt({
|
|
95
|
+
index: events.length + 1, track: 'MARKER', source: '',
|
|
96
|
+
recIn: tcToFrames(loc[1], fps), recOut: null,
|
|
97
|
+
name: loc[3].trim() || undefined, color: loc[2], fps,
|
|
98
|
+
}));
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
86
101
|
const tokens = line.split(/\s+/);
|
|
87
102
|
if (!/^\d+$/.test(tokens[0])) continue; // not an event line
|
|
88
103
|
const tcs = tokens.filter(isTc);
|
|
@@ -147,6 +162,14 @@ export function parseOTIO(otio, opts = {}) {
|
|
|
147
162
|
}
|
|
148
163
|
}
|
|
149
164
|
const src = (child.media_reference && (child.media_reference.target_url || child.media_reference.name)) || child.name || 'UNKNOWN';
|
|
165
|
+
for (const mk of child.markers || []) {
|
|
166
|
+
const mrStart = (mk.marked_range && mk.marked_range.start_time && mk.marked_range.start_time.value) || 0;
|
|
167
|
+
events.push(evt({
|
|
168
|
+
index: idx++, track: 'MARKER', source: '',
|
|
169
|
+
recIn: rec + (mrStart - startVal), recOut: null,
|
|
170
|
+
name: mk.name || undefined, color: mk.color || undefined, fps: rate,
|
|
171
|
+
}));
|
|
172
|
+
}
|
|
150
173
|
events.push(
|
|
151
174
|
evt({
|
|
152
175
|
index: idx++,
|
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.119.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()}")
|