davinci-resolve-mcp 2.68.2 → 2.69.1
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/CHANGELOG.md +184 -0
- package/README.md +46 -3
- package/docs/SKILL.md +181 -1
- package/docs/install.md +27 -1
- package/install.py +26 -1
- package/package.json +1 -1
- package/resolve-advanced/LICENSE +21 -0
- package/resolve-advanced/package.json +2 -1
- package/resolve-advanced/vendor/conform-qc/package.json +1 -0
- package/resolve-advanced/vendor/drp-format/package.json +1 -0
- package/resolve-advanced/vendor/drt-format/package.json +1 -0
- package/scripts/doctor.py +129 -3
- package/src/granular/common.py +1 -1
- package/src/server.py +522 -3
- package/src/utils/beat_detection.py +242 -0
- package/src/utils/bridge_differential.py +68 -2
- package/src/utils/broll_placement.py +201 -0
- package/src/utils/conform_lint.py +437 -0
- package/src/utils/edit_engine.py +633 -1
- package/src/utils/edit_handles.py +206 -0
- package/src/utils/edit_report.py +360 -0
- package/src/utils/first_impression.py +212 -0
- package/src/utils/prebalance.py +471 -0
- package/src/utils/project_journal.py +360 -0
- package/src/utils/reference_match.py +203 -0
- package/src/utils/rhythm_audit.py +252 -0
- package/src/utils/rule_of_six.py +217 -0
- package/src/utils/setup_sheet.py +121 -0
- package/src/utils/shot_assembly.py +259 -0
- package/src/utils/silence_ripple.py +89 -2
- package/src/utils/sound_density.py +253 -0
- package/src/utils/split_edits.py +180 -0
- package/src/utils/take_ranking.py +206 -0
- package/src/utils/transcript_edit.py +101 -5
- package/src/utils/turnover.py +187 -0
package/scripts/doctor.py
CHANGED
|
@@ -203,6 +203,121 @@ print(json.dumps(payload))
|
|
|
203
203
|
return payload
|
|
204
204
|
|
|
205
205
|
|
|
206
|
+
def detect_edition(probe: dict[str, Any]) -> str | None:
|
|
207
|
+
""""Studio" / "Free" from the connected product name, or None if unknown.
|
|
208
|
+
|
|
209
|
+
Only knowable when something answered. Guessing from an install path would
|
|
210
|
+
be wrong on any machine running both, which is common — the sandboxed free
|
|
211
|
+
edition and a direct-download Studio coexist happily.
|
|
212
|
+
"""
|
|
213
|
+
product = str(probe.get("product") or "")
|
|
214
|
+
if not product:
|
|
215
|
+
return None
|
|
216
|
+
return "Studio" if "studio" in product.lower() else "Free"
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def bridge_checks(probe: dict[str, Any]) -> list[dict[str, str]]:
|
|
220
|
+
"""Report the free-edition in-app bridge as a first-class path.
|
|
221
|
+
|
|
222
|
+
The bridge shipped in v2.68.0 and nothing in the diagnostics mentioned it,
|
|
223
|
+
so a free-edition user running doctor saw a wall of failures and no way
|
|
224
|
+
through — which is precisely the conclusion the docs used to push them to.
|
|
225
|
+
"""
|
|
226
|
+
results: list[dict[str, str]] = []
|
|
227
|
+
installer = REPO / "scripts" / "install_resolve_bridge.py"
|
|
228
|
+
enabled = os.environ.get("DAVINCI_RESOLVE_BRIDGE", "").strip().lower() not in ("", "0", "false", "no", "off")
|
|
229
|
+
|
|
230
|
+
installed_at: list[str] = []
|
|
231
|
+
try:
|
|
232
|
+
sys.path.insert(0, str(REPO / "scripts"))
|
|
233
|
+
import install_resolve_bridge as _bridge # type: ignore
|
|
234
|
+
|
|
235
|
+
for folder in _bridge.script_targets():
|
|
236
|
+
if (folder / "resolve_bridge.py").exists():
|
|
237
|
+
installed_at.append(str(folder))
|
|
238
|
+
except Exception:
|
|
239
|
+
# Never let a diagnostic tool be the thing that crashes.
|
|
240
|
+
installed_at = []
|
|
241
|
+
|
|
242
|
+
if installed_at:
|
|
243
|
+
check(results, "OK", "Free-edition bridge", f"installed: {installed_at[0]}")
|
|
244
|
+
elif installer.exists():
|
|
245
|
+
check(
|
|
246
|
+
results, "INFO", "Free-edition bridge",
|
|
247
|
+
"not installed. Only needed on the FREE edition (Studio uses external "
|
|
248
|
+
f"scripting directly). Install: {PYTHON} {installer}",
|
|
249
|
+
)
|
|
250
|
+
else:
|
|
251
|
+
check(results, "WARN", "Free-edition bridge", f"installer missing: {installer}")
|
|
252
|
+
|
|
253
|
+
edition = detect_edition(probe)
|
|
254
|
+
if edition:
|
|
255
|
+
check(results, "OK", "Resolve edition", edition)
|
|
256
|
+
elif installed_at or enabled:
|
|
257
|
+
check(results, "INFO", "Resolve edition",
|
|
258
|
+
"unknown — nothing answered, so the edition cannot be read")
|
|
259
|
+
|
|
260
|
+
if enabled:
|
|
261
|
+
connected = bool(probe.get("resolve_connected"))
|
|
262
|
+
check(
|
|
263
|
+
results, "OK" if connected else "WARN", "DAVINCI_RESOLVE_BRIDGE",
|
|
264
|
+
"set — the bridge is the only transport tried"
|
|
265
|
+
+ ("" if connected else ". Nothing answered: in Resolve run "
|
|
266
|
+
"Workspace > Scripts > resolve_bridge (a saved project must be open)."),
|
|
267
|
+
)
|
|
268
|
+
elif installed_at:
|
|
269
|
+
check(
|
|
270
|
+
results, "INFO", "DAVINCI_RESOLVE_BRIDGE",
|
|
271
|
+
"not set — the bridge is installed but will not be used. "
|
|
272
|
+
"export DAVINCI_RESOLVE_BRIDGE=1 to enable it.",
|
|
273
|
+
)
|
|
274
|
+
return results
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
#: Optional extras and what each unlocks. Absent is INFO, never FAIL — the core
|
|
278
|
+
#: install is deliberately small and every feature below refuses honestly with
|
|
279
|
+
#: its own install line rather than degrading into a guess.
|
|
280
|
+
OPTIONAL_EXTRAS = (
|
|
281
|
+
("numpy", "colour pre-balance, reference-still matching, sound-density audit", "pip install numpy"),
|
|
282
|
+
("librosa", "beat / bar / phrase detection for music-driven cutting", "pip install librosa"),
|
|
283
|
+
("whisper", "transcription, and the word-level tools built on it", "pip install -U openai-whisper"),
|
|
284
|
+
("open_clip", "visual similarity and find_similar", "pip install open_clip_torch"),
|
|
285
|
+
("transformers", "CLAP audio embeddings", "pip install transformers"),
|
|
286
|
+
("cv2", "additional frame analysis", "pip install opencv-python"),
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def extras_checks() -> list[dict[str, str]]:
|
|
291
|
+
"""Report which optional extras are installed and what each would unlock.
|
|
292
|
+
|
|
293
|
+
Absence is informational. The point is that a user should not have to
|
|
294
|
+
discover an extra by hitting a refusal mid-workflow — "setup too hard" was
|
|
295
|
+
one of the loudest complaints this release set out to answer, and shipping
|
|
296
|
+
features behind an undocumented `pip install` is the same failure.
|
|
297
|
+
"""
|
|
298
|
+
import importlib.util
|
|
299
|
+
|
|
300
|
+
results: list[dict[str, str]] = []
|
|
301
|
+
missing: list[str] = []
|
|
302
|
+
for module, unlocks, install in OPTIONAL_EXTRAS:
|
|
303
|
+
try:
|
|
304
|
+
present = importlib.util.find_spec(module) is not None
|
|
305
|
+
except (ImportError, ValueError):
|
|
306
|
+
present = False
|
|
307
|
+
if present:
|
|
308
|
+
check(results, "OK", f"Extra: {module}", unlocks)
|
|
309
|
+
else:
|
|
310
|
+
missing.append(module)
|
|
311
|
+
check(results, "INFO", f"Extra: {module}", f"not installed — {unlocks} ({install})")
|
|
312
|
+
if missing:
|
|
313
|
+
check(
|
|
314
|
+
results, "INFO", "Optional extras",
|
|
315
|
+
f"{len(missing)} of {len(OPTIONAL_EXTRAS)} absent. None are required; each "
|
|
316
|
+
"feature refuses with its install line rather than guessing.",
|
|
317
|
+
)
|
|
318
|
+
return results
|
|
319
|
+
|
|
320
|
+
|
|
206
321
|
def collect(
|
|
207
322
|
resolve_host: str | None = None,
|
|
208
323
|
resolve_timeout: float | None = None,
|
|
@@ -240,17 +355,28 @@ def collect(
|
|
|
240
355
|
str(probe["connection_error"]),
|
|
241
356
|
)
|
|
242
357
|
else:
|
|
358
|
+
# This is also exactly what the free edition looks like: the module
|
|
359
|
+
# imports fine and scriptapp refuses, because Blackmagic gates
|
|
360
|
+
# *external* scripting to Studio. Sending someone to toggle a
|
|
361
|
+
# preference that cannot help them is a dead end, so name the bridge
|
|
362
|
+
# here too.
|
|
243
363
|
check(
|
|
244
364
|
results,
|
|
245
365
|
"WARN",
|
|
246
366
|
"Resolve scripting connection",
|
|
247
|
-
"Module import worked, but scriptapp returned no object.
|
|
248
|
-
"External scripting = Local, or Network with --resolve-host "
|
|
249
|
-
"
|
|
367
|
+
"Module import worked, but scriptapp returned no object. On Studio: "
|
|
368
|
+
"External scripting = Local, or Network with --resolve-host set to "
|
|
369
|
+
"the Resolve host IP, then restart Resolve. On the FREE edition "
|
|
370
|
+
"external scripting is gated off entirely — use the in-app bridge "
|
|
371
|
+
"(see 'Free-edition bridge' below).",
|
|
250
372
|
)
|
|
251
373
|
else:
|
|
252
374
|
check(results, "FAIL", "DaVinciResolveScript import", str(probe.get("error")))
|
|
253
375
|
|
|
376
|
+
results.extend(bridge_checks(probe))
|
|
377
|
+
|
|
378
|
+
results.extend(extras_checks())
|
|
379
|
+
|
|
254
380
|
check(results, "OK", "MCP server version", version_from_server())
|
|
255
381
|
check(results, "OK", "MCP git head", git_head())
|
|
256
382
|
check(results, "OK", "Git status", git_summary())
|
package/src/granular/common.py
CHANGED
|
@@ -85,7 +85,7 @@ if not logging.getLogger().handlers:
|
|
|
85
85
|
handlers=[logging.StreamHandler()],
|
|
86
86
|
)
|
|
87
87
|
|
|
88
|
-
VERSION = "2.
|
|
88
|
+
VERSION = "2.69.1"
|
|
89
89
|
logger = logging.getLogger("davinci-resolve-mcp")
|
|
90
90
|
logger.info(f"Starting DaVinci Resolve MCP Server v{VERSION}")
|
|
91
91
|
logger.info(f"Detected platform: {get_platform()}")
|