davinci-resolve-mcp 2.129.0 → 2.130.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.
@@ -52,6 +52,7 @@ window. `render.verify_output` covers the container-level checks.
52
52
  | Turnover markers | EDL `* LOC:` locators + OTIO markers → authored | v2.119 |
53
53
  | TC-bearing sources (AAF route) | native clip capture (`MediaStartTime`), channel-leg merge | v2.120–2.122 |
54
54
  | Subtitles | `spec.subtitles` / `spec.subtitlesSrt` (raw SRT) | v2.128 |
55
+ | Compound clips | survive `extract_from_drp` → `.drt` (inner containers kept, recursive) | v2.130 |
55
56
  | Fusion titles | `elements: [{type:'title', text}]` — **21-gen hosts only** | v2.108 |
56
57
 
57
58
  `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.129.0"
40
+ VERSION = "2.130.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.129.0",
3
+ "version": "2.130.0",
4
4
  "description": "NPM bootstrapper for the DaVinci Resolve MCP Server.",
5
5
  "license": "MIT",
6
6
  "author": "Samuel Gursky <samgursky@gmail.com>",
@@ -373,6 +373,45 @@ export const drtTool = {
373
373
  const keepEntry = seqEntries[idx];
374
374
  const keepXml = await drpZip.file(keepEntry).async('string');
375
375
  const keepSeqIds = [...keepXml.matchAll(/<Sequence>([0-9a-f-]{36})<\/Sequence>/g)].map((m) => m[1]);
376
+ // COMPOUND CLIPS: a compound is a pool Sm2MpCompoundClip with an
377
+ // EMBEDDED Sm2Sequence whose tracks live in their OWN SeqContainer —
378
+ // dropping that container ships a compound that imports and reads back
379
+ // but is hollow. Walk MediaRefs of the kept container(s) → compound
380
+ // pool elements → embedded sequence ids → keep those containers too,
381
+ // recursively (compounds nest). Live-proven: a .drt that keeps the
382
+ // inner container renders the compound's content (E45, 19.1.3.7).
383
+ const mpXmlByName = {};
384
+ for (const name of Object.keys(drpZip.files)) {
385
+ if (!drpZip.files[name].dir && name.endsWith('MpFolder.xml')) {
386
+ mpXmlByName[name] = await drpZip.file(name).async('string');
387
+ }
388
+ }
389
+ const seqXmlByEntry = { [keepEntry]: keepXml };
390
+ const keepContainers = new Set([keepEntry]);
391
+ const compoundIds = new Set();
392
+ const queue = [keepXml];
393
+ while (queue.length) {
394
+ const xml = queue.pop();
395
+ for (const m of xml.matchAll(/<MediaRef>([0-9a-f-]{36})<\/MediaRef>/g)) {
396
+ const ref = m[1];
397
+ if (compoundIds.has(ref)) continue;
398
+ for (const mpXml of Object.values(mpXmlByName)) {
399
+ const cm = mpXml.match(new RegExp(`<Sm2MpCompoundClip DbId="${ref}">[\\s\\S]*?</Sm2MpCompoundClip>`));
400
+ if (!cm) continue;
401
+ compoundIds.add(ref);
402
+ const innerSeq = (cm[0].match(/<Sm2Sequence DbId="([0-9a-f-]{36})">/) || [])[1];
403
+ if (!innerSeq) continue;
404
+ for (const entryName of seqEntries) {
405
+ if (keepContainers.has(entryName)) continue;
406
+ const sx = seqXmlByEntry[entryName] ?? (seqXmlByEntry[entryName] = await drpZip.file(entryName).async('string'));
407
+ if (sx.includes(`<Sequence>${innerSeq}</Sequence>`)) {
408
+ keepContainers.add(entryName);
409
+ queue.push(sx);
410
+ }
411
+ }
412
+ }
413
+ }
414
+ }
376
415
  const out = new JSZip();
377
416
  let droppedTimelines = 0;
378
417
  for (const name of Object.keys(drpZip.files)) {
@@ -380,7 +419,7 @@ export const drtTool = {
380
419
  if (entry.dir) continue;
381
420
  if (name === 'Gallery.xml') continue;
382
421
  const isSeq = seqEntries.includes(name);
383
- if (isSeq && name !== keepEntry) continue;
422
+ if (isSeq && !keepContainers.has(name)) continue;
384
423
  let content = await entry.async(name.endsWith('.xml') ? 'string' : 'nodebuffer');
385
424
  if (name.endsWith('MpFolder.xml') && seqEntries.length > 1) {
386
425
  content = content.replace(
@@ -87,7 +87,7 @@ if not logging.getLogger().handlers:
87
87
  handlers=[logging.StreamHandler()],
88
88
  )
89
89
 
90
- VERSION = "2.129.0"
90
+ VERSION = "2.130.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.129.0"
14
+ VERSION = "2.130.0"
15
15
 
16
16
  import base64
17
17
  import os