davinci-resolve-mcp 2.126.0 → 2.127.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/install.py CHANGED
@@ -37,7 +37,7 @@ from src.utils.update_check import (
37
37
 
38
38
  # ─── Version ──────────────────────────────────────────────────────────────────
39
39
 
40
- VERSION = "2.126.0"
40
+ VERSION = "2.127.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.126.0",
3
+ "version": "2.127.0",
4
4
  "description": "NPM bootstrapper for the DaVinci Resolve MCP Server.",
5
5
  "license": "MIT",
6
6
  "author": "Samuel Gursky <samgursky@gmail.com>",
@@ -133,7 +133,9 @@ export async function parseAafDocument(contentOrPath) {
133
133
  const { sequences } = await runProbe(aafPath);
134
134
  const events = [];
135
135
  for (const seq of sequences || []) for (const ev of seq.events || []) events.push(ev);
136
- return { events, sequences: (sequences || []).map(sequenceSummary) };
136
+ // Summaries PLUS each sequence's own events the assemble picker needs
137
+ // per-sequence events without a second probe run.
138
+ return { events, sequences: (sequences || []).map((sq) => ({ ...sequenceSummary(sq), events: sq.events || [] })) };
137
139
  }
138
140
 
139
141
  /**
@@ -382,11 +382,16 @@ export function eventsToAssembleSpec(events, opts = {}) {
382
382
  }
383
383
  }
384
384
 
385
- const media = [...perSource.entries()].map(([reel, cuts]) => ({
386
- mediaFilePath: sourceMap[reel].mediaFilePath,
387
- spec: sourceMap[reel].spec,
388
- cuts,
389
- }));
385
+ // Reel aliasing: multiple reels legitimately map to ONE file (Avid mob
386
+ // names vs tape names, re-linked dailies). Group by mediaFilePath so the
387
+ // assembly sees one source per FILE, not per reel.
388
+ const byFile = new Map();
389
+ for (const [reel, cuts] of perSource.entries()) {
390
+ const fp = sourceMap[reel].mediaFilePath;
391
+ if (!byFile.has(fp)) byFile.set(fp, { mediaFilePath: fp, spec: sourceMap[reel].spec, cuts: [] });
392
+ byFile.get(fp).cuts.push(...cuts);
393
+ }
394
+ const media = [...byFile.values()];
390
395
 
391
396
  // Author cross-dissolves where the geometry allows it (render-verified on
392
397
  // 19.1.3.7: an offline Sm2TiTransition over transplanted cross-source media
@@ -41,6 +41,8 @@ const assembleFromInterchangeSchema = z.object({
41
41
  outputPath: z.string().describe('Where the importable .drt is written'),
42
42
  targetAppVersion: z.union([z.string(), z.number()]).optional()
43
43
  .describe("Host Resolve version, e.g. '19.1' for pre-21"),
44
+ sequenceName: z.string().optional().describe('Multi-sequence AAF/prproj: assemble THIS sequence (see editorial.list_sequences)'),
45
+ sequenceIndex: z.number().int().optional().describe('Multi-sequence AAF/prproj: assemble the sequence at this 0-based index'),
44
46
  preserveStartTimecode: z.boolean().optional()
45
47
  .describe('Keep the interchange\'s ABSOLUTE record start: the assembled timeline starts at the turnover\'s real first record frame instead of 01:00:00:00 (start-TC patch render-verified on 19). AAF conforms should pass true.'),
46
48
  });
@@ -212,22 +214,42 @@ export const drtTool = {
212
214
  const { eventsToAssembleSpec } = await import('../author-interchange.mjs');
213
215
  let content = p.content;
214
216
  let events;
217
+ const pickSequence = (sequences) => {
218
+ // Multi-sequence containers (AAF/prproj): pick ONE sequence by name
219
+ // or index instead of flattening everything (overlapping record
220
+ // ranges across sequences would refuse in the overlap check).
221
+ if (p.sequenceName !== undefined) {
222
+ const hit = sequences.find((sq) => sq.name === p.sequenceName);
223
+ if (!hit) throw new Error(`sequenceName ${JSON.stringify(p.sequenceName)} not found — available: ${sequences.map((sq) => sq.name).join(', ')}`);
224
+ return hit.events;
225
+ }
226
+ if (p.sequenceIndex !== undefined) {
227
+ if (p.sequenceIndex < 0 || p.sequenceIndex >= sequences.length) throw new Error(`sequenceIndex ${p.sequenceIndex} out of range (${sequences.length} sequences)`);
228
+ return sequences[p.sequenceIndex].events;
229
+ }
230
+ if (sequences.length > 1) {
231
+ const nonEmpty = sequences.filter((sq) => (sq.events || []).length);
232
+ if (nonEmpty.length > 1) throw new Error(
233
+ `the file holds ${nonEmpty.length} sequences with events — pass sequenceName or sequenceIndex ` +
234
+ `(available: ${sequences.map((sq, i) => `${i}:${sq.name}`).join(', ')})`);
235
+ if (nonEmpty.length === 1) return nonEmpty[0].events;
236
+ }
237
+ return sequences.flatMap((sq) => sq.events || []);
238
+ };
215
239
  if (p.format === 'aaf') {
216
- // BUG FIX: this branch used to fall through to the sync
217
- // parseInterchange, which THROWS for aaf ("parse it via the async
218
- // AAF path") — the tool-layer AAF route never worked until now.
240
+ // BUG FIX (v2.126.0): this branch used to fall through to the sync
241
+ // parseInterchange, which THROWS for aaf the tool-layer AAF route
242
+ // never worked before.
219
243
  if (!p.path) return { error: 'aaf input requires path' };
220
- const { parseAAF } = await import('../aaf.mjs');
221
- const parsed = await parseAAF(p.path);
222
- events = Array.isArray(parsed) ? parsed : parsed.events;
244
+ const { parseAafDocument } = await import('../aaf.mjs');
245
+ const parsed = await parseAafDocument(p.path);
246
+ events = pickSequence(parsed.sequences);
223
247
  } else if (p.format === 'prproj') {
224
- // Premiere: offline gunzip+graph read (no Premiere, no Resolve import
225
- // path). parsePrproj flattens EVERY sequence's events; a multi-sequence
226
- // .prproj with overlapping record ranges refuses naturally in the
227
- // overlap check — pre-split via editorial.list_sequences if needed.
248
+ // Premiere: offline gunzip+graph read (no Premiere, no Resolve
249
+ // import path).
228
250
  if (!p.path) return { error: 'prproj input requires path' };
229
- const { parsePrproj } = await import('../prproj.mjs');
230
- events = parsePrproj(p.path);
251
+ const { parsePrprojDoc } = await import('../prproj.mjs');
252
+ events = pickSequence(parsePrprojDoc(p.path).sequences);
231
253
  } else if (!content) {
232
254
  if (!p.path) return { error: 'provide content or path' };
233
255
  content = await fs.readFile(p.path, 'utf8');
@@ -87,7 +87,7 @@ if not logging.getLogger().handlers:
87
87
  handlers=[logging.StreamHandler()],
88
88
  )
89
89
 
90
- VERSION = "2.126.0"
90
+ VERSION = "2.127.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.126.0"
14
+ VERSION = "2.127.0"
15
15
 
16
16
  import base64
17
17
  import os