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 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
- [![Version](https://img.shields.io/badge/version-2.33.0-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
3
+ [![Version](https://img.shields.io/badge/version-2.33.1-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
4
4
  [![npm](https://img.shields.io/npm/v/davinci-resolve-mcp.svg?label=npm&color=CB3837)](https://www.npmjs.com/package/davinci-resolve-mcp)
5
5
  [![API Coverage](https://img.shields.io/badge/API%20Coverage-100%25-brightgreen.svg)](docs/reference/api-coverage.md)
6
6
  [![Tools](https://img.shields.io/badge/MCP%20Tools-32%20(341%20full)-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?)`, `perform_audio_classification`,
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.0"
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "davinci-resolve-mcp",
3
- "version": "2.33.0",
3
+ "version": "2.33.1",
4
4
  "description": "NPM bootstrapper for the DaVinci Resolve MCP Server.",
5
5
  "license": "MIT",
6
6
  "author": "Samuel Gursky <samgursky@gmail.com>",
@@ -80,7 +80,7 @@ if not logging.getLogger().handlers:
80
80
  handlers=[logging.StreamHandler()],
81
81
  )
82
82
 
83
- VERSION = "2.33.0"
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.0"
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
- return {"success": bool(mp.AutoSyncAudio(clips, settings)), "count": len(clips), "missing": missing, "settings": settings}
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
  # ═══════════════════════════════════════════════════════════════════════════════