davinci-resolve-mcp 2.125.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.
|
|
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
|
@@ -18,7 +18,13 @@ const PROBE = fileURLToPath(new URL('./aaf_probe.py', import.meta.url));
|
|
|
18
18
|
|
|
19
19
|
/** python interpreter — overridable for environments/tests (must have `aaf2` importable). */
|
|
20
20
|
function pythonCmd() {
|
|
21
|
-
|
|
21
|
+
if (process.env.AAF_PROBE_PYTHON) return process.env.AAF_PROBE_PYTHON;
|
|
22
|
+
if (process.env.PYTHON) return process.env.PYTHON;
|
|
23
|
+
// The repo venv carries pyaaf2; a bare python3 usually does not. Prefer it
|
|
24
|
+
// when present so the tool path works without env plumbing.
|
|
25
|
+
const venvPy = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..', 'venv', 'bin', 'python');
|
|
26
|
+
if (existsSync(venvPy)) return venvPy;
|
|
27
|
+
return 'python3';
|
|
22
28
|
}
|
|
23
29
|
|
|
24
30
|
const REMEDIATION =
|
|
@@ -127,7 +133,9 @@ export async function parseAafDocument(contentOrPath) {
|
|
|
127
133
|
const { sequences } = await runProbe(aafPath);
|
|
128
134
|
const events = [];
|
|
129
135
|
for (const seq of sequences || []) for (const ev of seq.events || []) events.push(ev);
|
|
130
|
-
|
|
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 || [] })) };
|
|
131
139
|
}
|
|
132
140
|
|
|
133
141
|
/**
|
|
@@ -382,11 +382,16 @@ export function eventsToAssembleSpec(events, opts = {}) {
|
|
|
382
382
|
}
|
|
383
383
|
}
|
|
384
384
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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,17 +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') {
|
|
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.
|
|
216
243
|
if (!p.path) return { error: 'aaf input requires path' };
|
|
217
|
-
|
|
244
|
+
const { parseAafDocument } = await import('../aaf.mjs');
|
|
245
|
+
const parsed = await parseAafDocument(p.path);
|
|
246
|
+
events = pickSequence(parsed.sequences);
|
|
218
247
|
} else if (p.format === 'prproj') {
|
|
219
|
-
// Premiere: offline gunzip+graph read (no Premiere, no Resolve
|
|
220
|
-
// path).
|
|
221
|
-
// .prproj with overlapping record ranges refuses naturally in the
|
|
222
|
-
// overlap check — pre-split via editorial.list_sequences if needed.
|
|
248
|
+
// Premiere: offline gunzip+graph read (no Premiere, no Resolve
|
|
249
|
+
// import path).
|
|
223
250
|
if (!p.path) return { error: 'prproj input requires path' };
|
|
224
|
-
const {
|
|
225
|
-
events =
|
|
251
|
+
const { parsePrprojDoc } = await import('../prproj.mjs');
|
|
252
|
+
events = pickSequence(parsePrprojDoc(p.path).sequences);
|
|
226
253
|
} else if (!content) {
|
|
227
254
|
if (!p.path) return { error: 'provide content or path' };
|
|
228
255
|
content = await fs.readFile(p.path, 'utf8');
|
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.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()}")
|