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 +14 -0
- package/README.md +1 -1
- package/docs/SKILL.md +5 -1
- package/install.py +1 -1
- package/package.json +1 -1
- package/src/granular/common.py +1 -1
- package/src/server.py +98 -3
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
|
-
[](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
|
@@ -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.
|
|
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
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.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.
|
|
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
|
# ═══════════════════════════════════════════════════════════════════════════════
|