davinci-resolve-mcp 2.33.0 → 2.33.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 +15 -0
- package/README.md +1 -1
- package/docs/SKILL.md +4 -1
- package/install.py +1 -1
- package/package.json +1 -1
- package/src/granular/common.py +1 -1
- package/src/server.py +67 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
Release history for the DaVinci Resolve MCP Server. The latest release is summarized in the root README; older entries live here to keep the README focused.
|
|
4
4
|
|
|
5
|
+
## What's New in v2.33.1
|
|
6
|
+
|
|
7
|
+
Clip transcription read-back and more trustworthy auto-sync reporting.
|
|
8
|
+
|
|
9
|
+
- **Added** `media_pool_item(action="get_transcription")` returns
|
|
10
|
+
`{text, truncated, status, has_transcription}`. Transcription could previously
|
|
11
|
+
be triggered but never read back. Resolve's `Transcription` clip property is a
|
|
12
|
+
preview that ends in an ellipsis when the full transcript is longer, so
|
|
13
|
+
`truncated` tells callers the returned text is partial.
|
|
14
|
+
- **Changed** Auto-sync audio now verifies linkage by reading each clip's
|
|
15
|
+
`Synced Audio` property before and after the call and reporting
|
|
16
|
+
`linked` / `newly_linked` / `already_linked`, instead of trusting
|
|
17
|
+
`AutoSyncAudio`'s unreliable boolean. The boolean is still returned as
|
|
18
|
+
`success`, but callers should trust `linked`.
|
|
19
|
+
|
|
5
20
|
## What's New in v2.33.0
|
|
6
21
|
|
|
7
22
|
Fusion node-graph layout and duplication, plus performance and robustness
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# DaVinci Resolve MCP Server
|
|
2
2
|
|
|
3
|
-
[](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
|
|
4
4
|
[](https://www.npmjs.com/package/davinci-resolve-mcp)
|
|
5
5
|
[](docs/reference/api-coverage.md)
|
|
6
6
|
[-blue.svg)](#server-modes)
|
package/docs/SKILL.md
CHANGED
|
@@ -455,7 +455,10 @@ Key actions: `get_name`, `get_metadata(key?)`, `set_metadata(key, value)`,
|
|
|
455
455
|
`set_clip_color(color)`, `link_proxy(proxy_path)`, `replace_clip(path)`,
|
|
456
456
|
`set_name(name)`, `link_full_resolution_media(path)`,
|
|
457
457
|
`replace_clip_preserve_sub_clip(path)`, `monitor_growing_file`,
|
|
458
|
-
`transcribe_audio(use_speaker_detection?)`, `
|
|
458
|
+
`transcribe_audio(use_speaker_detection?)`, `clear_transcription`,
|
|
459
|
+
`get_transcription` (read back `{text, truncated, status, has_transcription}`;
|
|
460
|
+
`truncated` flags when Resolve's preview cut the text off),
|
|
461
|
+
`perform_audio_classification`,
|
|
459
462
|
`analyze_for_intellisearch(identify_faces?, is_better_mode?)`, `analyze_for_slate(marker_color?)`,
|
|
460
463
|
`remove_motion_blur(deblur_option?)` (Resolve 21+; AI Extras / confirm-token gated as noted above),
|
|
461
464
|
`get_audio_mapping`, `get_mark_in_out`, `set_mark_in_out`
|
package/install.py
CHANGED
|
@@ -35,7 +35,7 @@ from src.utils.update_check import (
|
|
|
35
35
|
|
|
36
36
|
# ─── Version ──────────────────────────────────────────────────────────────────
|
|
37
37
|
|
|
38
|
-
VERSION = "2.33.
|
|
38
|
+
VERSION = "2.33.1"
|
|
39
39
|
# Only hard floor: mcp[cli] requires Python 3.10+. There is no upper bound —
|
|
40
40
|
# Resolve's scripting bridge loads into newer interpreters on recent builds
|
|
41
41
|
# (Python 3.14 verified against Resolve Studio 20.3.2). Older Resolve builds
|
package/package.json
CHANGED
package/src/granular/common.py
CHANGED
|
@@ -80,7 +80,7 @@ if not logging.getLogger().handlers:
|
|
|
80
80
|
handlers=[logging.StreamHandler()],
|
|
81
81
|
)
|
|
82
82
|
|
|
83
|
-
VERSION = "2.33.
|
|
83
|
+
VERSION = "2.33.1"
|
|
84
84
|
logger = logging.getLogger("davinci-resolve-mcp")
|
|
85
85
|
logger.info(f"Starting DaVinci Resolve MCP Server v{VERSION}")
|
|
86
86
|
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 341-tool granular server instead
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
|
-
VERSION = "2.33.
|
|
14
|
+
VERSION = "2.33.1"
|
|
15
15
|
|
|
16
16
|
import base64
|
|
17
17
|
import os
|
|
@@ -1173,6 +1173,18 @@ def _requires_method(obj, method_name, min_version):
|
|
|
1173
1173
|
return None
|
|
1174
1174
|
return _err(f"{method_name} requires DaVinci Resolve {min_version}+")
|
|
1175
1175
|
|
|
1176
|
+
def _is_truncated(text):
|
|
1177
|
+
"""True if a transcription preview was cut off.
|
|
1178
|
+
|
|
1179
|
+
Resolve's `Transcription` clip property is a preview that ends with an
|
|
1180
|
+
ellipsis (… or ...) when the full transcript is longer than the property
|
|
1181
|
+
exposes, so the caller knows the returned text is partial.
|
|
1182
|
+
"""
|
|
1183
|
+
if not isinstance(text, str):
|
|
1184
|
+
return False
|
|
1185
|
+
t = text.rstrip()
|
|
1186
|
+
return t.endswith("…") or t.endswith("...")
|
|
1187
|
+
|
|
1176
1188
|
_MARKER_COLORS = [
|
|
1177
1189
|
"Blue", "Cyan", "Green", "Yellow", "Red", "Pink", "Purple", "Fuchsia",
|
|
1178
1190
|
"Rose", "Lavender", "Sky", "Mint", "Lemon", "Sand", "Cocoa", "Cream",
|
|
@@ -5408,6 +5420,22 @@ def _audio_mapping_report(mp, tl, p: Dict[str, Any]):
|
|
|
5408
5420
|
return {"timeline_items": timeline_items, "media_pool_items": clip_rows}
|
|
5409
5421
|
|
|
5410
5422
|
|
|
5423
|
+
def _clip_name(clip):
|
|
5424
|
+
try:
|
|
5425
|
+
return clip.GetName()
|
|
5426
|
+
except Exception:
|
|
5427
|
+
return None
|
|
5428
|
+
|
|
5429
|
+
|
|
5430
|
+
def _synced_audio(clip):
|
|
5431
|
+
"""Best-effort read of whether a clip currently has synced audio linked."""
|
|
5432
|
+
try:
|
|
5433
|
+
v = clip.GetClipProperty("Synced Audio")
|
|
5434
|
+
except Exception:
|
|
5435
|
+
return False
|
|
5436
|
+
return bool(v) and str(v).strip() not in ("", "None", "0", "Off", "No")
|
|
5437
|
+
|
|
5438
|
+
|
|
5411
5439
|
def _safe_auto_sync_audio(mp, p: Dict[str, Any]):
|
|
5412
5440
|
root = mp.GetRootFolder()
|
|
5413
5441
|
resolved, err = _clips_from_params(root, mp, p)
|
|
@@ -5421,7 +5449,26 @@ def _safe_auto_sync_audio(mp, p: Dict[str, Any]):
|
|
|
5421
5449
|
settings = _normalize_auto_sync_settings(dict(p.get("settings") or {}), get_resolve())
|
|
5422
5450
|
if p.get("dry_run", True):
|
|
5423
5451
|
return _ok(would_auto_sync=True, clips=_clip_summaries(clips), missing=missing, settings=settings)
|
|
5424
|
-
|
|
5452
|
+
# Read-back verification: AutoSyncAudio's boolean return is unreliable, so
|
|
5453
|
+
# capture each clip's "Synced Audio" linkage before and after and report the
|
|
5454
|
+
# delta. Trust `linked`/`newly_linked`, not `success`.
|
|
5455
|
+
before = [_synced_audio(c) for c in clips]
|
|
5456
|
+
ok = bool(mp.AutoSyncAudio(clips, settings))
|
|
5457
|
+
linked, newly_linked, already_linked = [], [], []
|
|
5458
|
+
for c, was in zip(clips, before):
|
|
5459
|
+
name = _clip_name(c)
|
|
5460
|
+
if _synced_audio(c):
|
|
5461
|
+
linked.append(name)
|
|
5462
|
+
(already_linked if was else newly_linked).append(name)
|
|
5463
|
+
return {
|
|
5464
|
+
"success": ok,
|
|
5465
|
+
"linked": linked,
|
|
5466
|
+
"newly_linked": newly_linked,
|
|
5467
|
+
"already_linked": already_linked,
|
|
5468
|
+
"count": len(clips),
|
|
5469
|
+
"missing": missing,
|
|
5470
|
+
"settings": settings,
|
|
5471
|
+
}
|
|
5425
5472
|
|
|
5426
5473
|
|
|
5427
5474
|
def _resolve_audio_constant(resolve_obj, name: str, fallback):
|
|
@@ -13626,6 +13673,9 @@ def media_pool_item(action: str, params: Optional[Dict[str, Any]] = None) -> Dic
|
|
|
13626
13673
|
get_unique_id(clip_id) -> {id}
|
|
13627
13674
|
transcribe_audio(clip_id, use_speaker_detection?) -> {success} — use_speaker_detection is Resolve 21+
|
|
13628
13675
|
clear_transcription(clip_id) -> {success}
|
|
13676
|
+
get_transcription(clip_id) -> {text, truncated, status, has_transcription}
|
|
13677
|
+
Read a clip's transcription. `truncated` flags when Resolve's preview
|
|
13678
|
+
property cut the text off (the full transcript is longer).
|
|
13629
13679
|
perform_audio_classification(clip_id) -> {success} — Resolve 21+
|
|
13630
13680
|
clear_audio_classification(clip_id) -> {success} — Resolve 21+
|
|
13631
13681
|
analyze_for_intellisearch(clip_id, identify_faces?, is_better_mode?) -> {success} — Resolve 21+, AI IntelliSearch Extra
|
|
@@ -13827,6 +13877,20 @@ def media_pool_item(action: str, params: Optional[Dict[str, Any]] = None) -> Dic
|
|
|
13827
13877
|
return {"success": bool(clip.TranscribeAudio(bool(usd)))}
|
|
13828
13878
|
elif action == "clear_transcription":
|
|
13829
13879
|
return {"success": bool(clip.ClearTranscription())}
|
|
13880
|
+
elif action == "get_transcription":
|
|
13881
|
+
raw = clip.GetClipProperty("Transcription")
|
|
13882
|
+
text = raw if isinstance(raw, str) else ("" if raw is None else str(raw))
|
|
13883
|
+
try:
|
|
13884
|
+
status = clip.GetClipProperty("Transcription Status")
|
|
13885
|
+
except Exception:
|
|
13886
|
+
status = None
|
|
13887
|
+
return {
|
|
13888
|
+
"clip_id": p.get("clip_id"),
|
|
13889
|
+
"text": text,
|
|
13890
|
+
"truncated": _is_truncated(text),
|
|
13891
|
+
"status": status or None,
|
|
13892
|
+
"has_transcription": bool(text.strip()),
|
|
13893
|
+
}
|
|
13830
13894
|
elif action == "perform_audio_classification":
|
|
13831
13895
|
missing = _requires_method(clip, "PerformAudioClassification", "21.0")
|
|
13832
13896
|
if missing:
|
|
@@ -13905,7 +13969,7 @@ def media_pool_item(action: str, params: Optional[Dict[str, Any]] = None) -> Dic
|
|
|
13905
13969
|
return {"success": bool(clip.SetMarkInOut(p["mark_in"], p["mark_out"], p.get("type", "all")))}
|
|
13906
13970
|
elif action == "clear_mark_in_out":
|
|
13907
13971
|
return {"success": bool(clip.ClearMarkInOut(p.get("type", "all")))}
|
|
13908
|
-
return _unknown(action, ["get_name","get_metadata","set_metadata","get_third_party_metadata","set_third_party_metadata","get_media_id","get_clip_property","set_clip_property","get_clip_color","set_clip_color","clear_clip_color","link_proxy","unlink_proxy","replace_clip","set_name","link_full_resolution_media","monitor_growing_file","replace_clip_preserve_sub_clip","get_unique_id","transcribe_audio","clear_transcription","perform_audio_classification","clear_audio_classification","analyze_for_intellisearch","analyze_for_slate","remove_motion_blur","get_audio_mapping","get_mark_in_out","set_mark_in_out","clear_mark_in_out","open_in_viewer"])
|
|
13972
|
+
return _unknown(action, ["get_name","get_metadata","set_metadata","get_third_party_metadata","set_third_party_metadata","get_media_id","get_clip_property","set_clip_property","get_clip_color","set_clip_color","clear_clip_color","link_proxy","unlink_proxy","replace_clip","set_name","link_full_resolution_media","monitor_growing_file","replace_clip_preserve_sub_clip","get_unique_id","transcribe_audio","clear_transcription","get_transcription","perform_audio_classification","clear_audio_classification","analyze_for_intellisearch","analyze_for_slate","remove_motion_blur","get_audio_mapping","get_mark_in_out","set_mark_in_out","clear_mark_in_out","open_in_viewer"])
|
|
13909
13973
|
|
|
13910
13974
|
|
|
13911
13975
|
# ═══════════════════════════════════════════════════════════════════════════════
|