davinci-resolve-mcp 2.33.2 → 2.33.3

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,20 @@
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.3
6
+
7
+ Two read tools that surface existing project state.
8
+
9
+ - **Added** `project_settings(action="project_summary")` returns a live
10
+ structural readout — current page, timeline count and current timeline, and a
11
+ media-pool inventory (folder/clip counts, clips by type, optional clip
12
+ sample). A cheap "what's in this project right now" snapshot that needs no
13
+ prior analysis.
14
+ - **Added** `timeline(action="get_transcript")` reads the current timeline's
15
+ subtitle track(s) as transcript text `{text, cue_count, has_subtitles, cues}`,
16
+ with optional per-cue timecodes. Complements the clip-level
17
+ `get_transcription`.
18
+
5
19
  ## What's New in v2.33.2
6
20
 
7
21
  Documentation: a guide for hand-authoring DaVinci Resolve `.setting` template
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.2-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
3
+ [![Version](https://img.shields.io/badge/version-2.33.3-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
@@ -402,7 +402,9 @@ Key actions: `get_name`, `set_name(name)`, `get_setting(name?)`,
402
402
  `set_setting(name, value)`, `get_color_groups`, `add_color_group(name)`,
403
403
  `delete_color_group(name)`, `export_frame_as_still(path)`,
404
404
  `load_burnin_preset(name)`, `insert_audio(media_path, ...)`,
405
- `apply_fairlight_preset(preset_name)`
405
+ `apply_fairlight_preset(preset_name)`,
406
+ `project_summary(include_clips?, clip_limit?)` — live structural readout
407
+ (current page, timeline count, media-pool inventory by type)
406
408
 
407
409
  ---
408
410
 
@@ -723,6 +725,8 @@ Key actions:
723
725
  - `get_current` — current timeline info
724
726
  - `set_current(index)` — switch timeline by 1-based index
725
727
  - `get_track_count(track_type)` — track_type: `"video"`, `"audio"`, `"subtitle"`
728
+ - `get_transcript(with_timecodes?)` — read the subtitle track(s) as transcript
729
+ text `{text, cue_count, has_subtitles, cues}`
726
730
  - `add_track(track_type, sub_type?)` / `delete_track(track_type, index)`
727
731
  - `get_items(track_type, index)` — items on a track
728
732
  - `clip_where(track_type?, track_index?, name_contains?, duration_lt?, duration_gt?)` —
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.2"
38
+ VERSION = "2.33.3"
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.2",
3
+ "version": "2.33.3",
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.2"
83
+ VERSION = "2.33.3"
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.2"
14
+ VERSION = "2.33.3"
15
15
 
16
16
  import base64
17
17
  import os
@@ -1885,6 +1885,89 @@ def _navigate_folder(mp, path):
1885
1885
  return current
1886
1886
 
1887
1887
 
1888
+ def _project_summary(proj, *, include_clips=False, clip_limit=50):
1889
+ """Live structural readout of a project: page, timelines, media-pool inventory.
1890
+
1891
+ Distinct from the analysis-derived bin summary — this is a cheap "what's in
1892
+ this project right now" snapshot that needs no prior analysis.
1893
+ """
1894
+ by_type: Dict[str, int] = {}
1895
+ sample_clips: List[Dict[str, Any]] = []
1896
+ folder_count = 0
1897
+ clip_count = 0
1898
+
1899
+ mp = proj.GetMediaPool()
1900
+ root = mp.GetRootFolder() if mp else None
1901
+
1902
+ def walk(folder):
1903
+ nonlocal folder_count, clip_count
1904
+ folder_count += 1
1905
+ for clip in (folder.GetClipList() or []):
1906
+ clip_count += 1
1907
+ try:
1908
+ ctype = clip.GetClipProperty("Type") or "Unknown"
1909
+ except Exception:
1910
+ ctype = "Unknown"
1911
+ by_type[ctype] = by_type.get(ctype, 0) + 1
1912
+ if include_clips and len(sample_clips) < clip_limit:
1913
+ sample_clips.append({
1914
+ "name": _clip_name(clip),
1915
+ "type": ctype,
1916
+ "id": clip.GetUniqueId(),
1917
+ })
1918
+ for sub in (folder.GetSubFolderList() or []):
1919
+ walk(sub)
1920
+
1921
+ if root:
1922
+ walk(root)
1923
+
1924
+ cur_tl = proj.GetCurrentTimeline()
1925
+ r = get_resolve()
1926
+ return {
1927
+ "project": proj.GetName(),
1928
+ "current_page": r.GetCurrentPage() if r else None,
1929
+ "timeline_count": proj.GetTimelineCount(),
1930
+ "current_timeline": cur_tl.GetName() if cur_tl else None,
1931
+ "media_pool": {
1932
+ "folder_count": folder_count,
1933
+ "clip_count": clip_count,
1934
+ "by_type": by_type,
1935
+ },
1936
+ "clips": sample_clips if include_clips else None,
1937
+ }
1938
+
1939
+
1940
+ def _timeline_transcript(tl, *, with_timecodes=False):
1941
+ """Read a timeline's subtitle track(s) as transcript text."""
1942
+ try:
1943
+ track_count = tl.GetTrackCount("subtitle")
1944
+ except Exception:
1945
+ track_count = 0
1946
+ cues: List[Dict[str, Any]] = []
1947
+ for ti in range(1, (track_count or 0) + 1):
1948
+ for item in (tl.GetItemListInTrack("subtitle", ti) or []):
1949
+ text = ""
1950
+ try:
1951
+ text = item.GetName() or ""
1952
+ except Exception:
1953
+ text = ""
1954
+ cue = {"text": text}
1955
+ if with_timecodes:
1956
+ try:
1957
+ cue["start"] = item.GetStart()
1958
+ cue["end"] = item.GetEnd()
1959
+ except Exception:
1960
+ pass
1961
+ cues.append(cue)
1962
+ full = " ".join(c["text"] for c in cues if c.get("text"))
1963
+ return {
1964
+ "text": full,
1965
+ "cue_count": len(cues),
1966
+ "has_subtitles": bool(cues),
1967
+ "cues": cues,
1968
+ }
1969
+
1970
+
1888
1971
  def _normalize_record_frame(
1889
1972
  ci: Dict[str, Any],
1890
1973
  index: int,
@@ -12484,6 +12567,8 @@ def project_settings(action: str, params: Optional[Dict[str, Any]] = None) -> Di
12484
12567
  refresh_luts() -> {success}
12485
12568
  get_gallery() -> {available}
12486
12569
  export_frame_as_still(path) -> {success}
12570
+ project_summary(include_clips?, clip_limit?) -> {project, current_page, timeline_count, current_timeline, media_pool}
12571
+ Live structural readout — what's in this project right now (no analysis needed).
12487
12572
  load_burnin_preset(name) -> {success}
12488
12573
  insert_audio(media_path, start_offset?, duration?) -> {success}
12489
12574
  get_color_groups() -> {groups}
@@ -12524,6 +12609,12 @@ def project_settings(action: str, params: Optional[Dict[str, Any]] = None) -> Di
12524
12609
  if parent and not os.path.isdir(parent):
12525
12610
  return _err(f"export_frame_as_still target directory does not exist: {parent}")
12526
12611
  return {"success": bool(proj.ExportCurrentFrameAsStill(still_path))}
12612
+ elif action == "project_summary":
12613
+ return _project_summary(
12614
+ proj,
12615
+ include_clips=bool(p.get("include_clips")),
12616
+ clip_limit=int(p.get("clip_limit", 50)),
12617
+ )
12527
12618
  elif action == "load_burnin_preset":
12528
12619
  return {"success": bool(proj.LoadBurnInPreset(p["name"]))}
12529
12620
  elif action == "insert_audio":
@@ -12584,7 +12675,7 @@ def project_settings(action: str, params: Optional[Dict[str, Any]] = None) -> Di
12584
12675
  return {"success": False}
12585
12676
  return {"success": True, "new": new_item.GetName(), "new_id": new_item.GetUniqueId(),
12586
12677
  "output_path": _rec.output_path, "output_bytes": _rec.output_bytes}
12587
- return _unknown(action, ["get_name","set_name","get_setting","set_setting","get_unique_id","get_presets","set_preset","refresh_luts","get_gallery","export_frame_as_still","load_burnin_preset","insert_audio","get_color_groups","add_color_group","delete_color_group","apply_fairlight_preset","generate_speech"])
12678
+ return _unknown(action, ["get_name","set_name","get_setting","set_setting","get_unique_id","get_presets","set_preset","refresh_luts","get_gallery","export_frame_as_still","project_summary","load_burnin_preset","insert_audio","get_color_groups","add_color_group","delete_color_group","apply_fairlight_preset","generate_speech"])
12588
12679
 
12589
12680
 
12590
12681
  # ═══════════════════════════════════════════════════════════════════════════════
@@ -15078,6 +15169,8 @@ def timeline(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str,
15078
15169
  get_unique_id() -> {id}
15079
15170
  get_node_graph() -> {available}
15080
15171
  get_media_pool_item() -> {name, id}
15172
+ get_transcript(with_timecodes?) -> {text, cue_count, has_subtitles, cues}
15173
+ Read the timeline's subtitle track(s) as transcript text.
15081
15174
  get_mark_in_out() -> {mark}
15082
15175
  set_mark_in_out(mark_in, mark_out, type?) -> {success}
15083
15176
  clear_mark_in_out(type?) -> {success}
@@ -15363,6 +15456,8 @@ def timeline(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str,
15363
15456
  elif action == "get_media_pool_item":
15364
15457
  mpi = tl.GetMediaPoolItem()
15365
15458
  return {"name": mpi.GetName(), "id": mpi.GetUniqueId()} if mpi else {"name": None, "id": None}
15459
+ elif action == "get_transcript":
15460
+ return _timeline_transcript(tl, with_timecodes=bool(p.get("with_timecodes")))
15366
15461
  elif action == "get_mark_in_out":
15367
15462
  return _ser(tl.GetMarkInOut())
15368
15463
  elif action == "set_mark_in_out":
@@ -15566,7 +15661,7 @@ def timeline(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str,
15566
15661
  if mp_err:
15567
15662
  return mp_err
15568
15663
  return _fairlight_boundary_report(proj, mp, tl, p)
15569
- return _unknown(action, ["list","get_current","set_current","get_name","set_name","get_start_frame","get_end_frame","get_start_timecode","set_start_timecode","get_track_count","add_track","delete_track","get_track_sub_type","set_track_enable","get_track_enabled","set_track_lock","get_track_locked","get_track_name","set_track_name","get_items","delete_clips","set_clips_linked","duplicate","duplicate_clips","copy_clips","move_clips","copy_range","duplicate_range","overwrite_range","lift_range","story_spine_report","create_variant_from_ranges","bulk_set_item_properties","apply_look_to_items","thumbnail_contact_sheet","marker_thumbnail_review","edit_kernel_capabilities","probe_edit_kernel_item","title_property_scan","set_title_text","bulk_set_title_text","create_compound_clip","create_fusion_clip","import_into_timeline","export","get_setting","set_setting","insert_generator","insert_fusion_generator","insert_fusion_composition","insert_ofx_generator","insert_title","insert_fusion_title","get_unique_id","get_node_graph","get_media_pool_item","get_mark_in_out","set_mark_in_out","clear_mark_in_out","convert_to_stereo","get_items_in_track","get_voice_isolation_state","set_voice_isolation_state","extract_source_frame_ranges","audio_mix_capability_report",*_TIMELINE_CONFORM_KERNEL_ACTIONS,*_TIMELINE_AUDIO_KERNEL_ACTIONS])
15664
+ return _unknown(action, ["list","get_current","set_current","get_name","set_name","get_start_frame","get_end_frame","get_start_timecode","set_start_timecode","get_track_count","add_track","delete_track","get_track_sub_type","set_track_enable","get_track_enabled","set_track_lock","get_track_locked","get_track_name","set_track_name","get_items","delete_clips","set_clips_linked","duplicate","duplicate_clips","copy_clips","move_clips","copy_range","duplicate_range","overwrite_range","lift_range","story_spine_report","create_variant_from_ranges","bulk_set_item_properties","apply_look_to_items","thumbnail_contact_sheet","marker_thumbnail_review","edit_kernel_capabilities","probe_edit_kernel_item","title_property_scan","set_title_text","bulk_set_title_text","create_compound_clip","create_fusion_clip","import_into_timeline","export","get_setting","set_setting","insert_generator","insert_fusion_generator","insert_fusion_composition","insert_ofx_generator","insert_title","insert_fusion_title","get_unique_id","get_node_graph","get_media_pool_item","get_transcript","get_mark_in_out","set_mark_in_out","clear_mark_in_out","convert_to_stereo","get_items_in_track","get_voice_isolation_state","set_voice_isolation_state","extract_source_frame_ranges","audio_mix_capability_report",*_TIMELINE_CONFORM_KERNEL_ACTIONS,*_TIMELINE_AUDIO_KERNEL_ACTIONS])
15570
15665
 
15571
15666
 
15572
15667
  # ═══════════════════════════════════════════════════════════════════════════════