davinci-resolve-mcp 2.23.0
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/AGENTS.md +85 -0
- package/CHANGELOG.md +802 -0
- package/CLAUDE.md +15 -0
- package/LICENSE +21 -0
- package/README.md +159 -0
- package/SECURITY.md +53 -0
- package/bin/davinci-resolve-mcp.mjs +376 -0
- package/docs/README.md +56 -0
- package/docs/SKILL.md +1145 -0
- package/docs/authoring/fuse-dctl-authoring.md +242 -0
- package/docs/authoring/script-plugin-authoring.md +195 -0
- package/docs/contributing.md +82 -0
- package/docs/guides/color-decision-guide.md +387 -0
- package/docs/guides/editorial-decision-guide.md +136 -0
- package/docs/guides/media-analysis-guide.md +615 -0
- package/docs/guides/multicam-setup-guide.md +138 -0
- package/docs/install.md +198 -0
- package/docs/integrations/workflow-integrations.md +120 -0
- package/docs/kernels/README.md +28 -0
- package/docs/kernels/audio-fairlight-kernel.md +86 -0
- package/docs/kernels/color-grade-kernel.md +103 -0
- package/docs/kernels/extension-authoring-kernel.md +101 -0
- package/docs/kernels/fusion-composition-kernel.md +91 -0
- package/docs/kernels/media-pool-ingest-kernel.md +147 -0
- package/docs/kernels/project-lifecycle-kernel.md +120 -0
- package/docs/kernels/render-deliver-kernel.md +92 -0
- package/docs/kernels/review-annotation-kernel.md +110 -0
- package/docs/kernels/timeline-conform-interchange-kernel.md +99 -0
- package/docs/kernels/timeline-edit-kernel.md +189 -0
- package/docs/notes/codec-plugin-notes.md +136 -0
- package/docs/notes/dctl-notes.md +234 -0
- package/docs/notes/fusion-template-notes.md +136 -0
- package/docs/notes/lut-notes.md +136 -0
- package/docs/notes/openfx-notes.md +120 -0
- package/docs/process/release-process.md +152 -0
- package/docs/reference/api-coverage.md +488 -0
- package/docs/reference/resolve_scripting_api.txt +1012 -0
- package/examples/README.md +53 -0
- package/examples/markers/README.md +81 -0
- package/examples/media/README.md +94 -0
- package/examples/timeline/README.md +98 -0
- package/install.py +1196 -0
- package/package.json +52 -0
- package/scripts/audit_api_parity.py +275 -0
- package/scripts/live_media_analysis_polish_probe.py +65 -0
- package/src/__init__.py +3 -0
- package/src/analysis_dashboard.py +4936 -0
- package/src/control_panel.py +13 -0
- package/src/granular/__init__.py +17 -0
- package/src/granular/common.py +727 -0
- package/src/granular/folder.py +287 -0
- package/src/granular/gallery.py +306 -0
- package/src/granular/graph.py +309 -0
- package/src/granular/media_pool.py +679 -0
- package/src/granular/media_pool_item.py +852 -0
- package/src/granular/media_storage.py +179 -0
- package/src/granular/project.py +1594 -0
- package/src/granular/resolve_control.py +521 -0
- package/src/granular/timeline.py +1074 -0
- package/src/granular/timeline_item.py +2251 -0
- package/src/resolve_mcp_server.py +43 -0
- package/src/server.py +15691 -0
- package/src/utils/__init__.py +3 -0
- package/src/utils/app_control.py +319 -0
- package/src/utils/audio_fairlight_live_probe.py +263 -0
- package/src/utils/cdl.py +20 -0
- package/src/utils/cloud_operations.py +192 -0
- package/src/utils/color_grade_live_probe.py +444 -0
- package/src/utils/dctl_templates.py +368 -0
- package/src/utils/extension_authoring_live_probe.py +292 -0
- package/src/utils/fuse_templates.py +1968 -0
- package/src/utils/fusion_composition_live_probe.py +284 -0
- package/src/utils/layout_presets.py +333 -0
- package/src/utils/mcp_stdio.py +32 -0
- package/src/utils/media_analysis.py +3618 -0
- package/src/utils/media_analysis_jobs.py +796 -0
- package/src/utils/media_pool_ingest_live_probe.py +592 -0
- package/src/utils/multicam.py +393 -0
- package/src/utils/object_inspection.py +287 -0
- package/src/utils/platform.py +157 -0
- package/src/utils/project_lifecycle_live_probe.py +376 -0
- package/src/utils/project_properties.py +601 -0
- package/src/utils/render_deliver_live_probe.py +384 -0
- package/src/utils/resolve_connection.py +77 -0
- package/src/utils/review_annotation_live_probe.py +352 -0
- package/src/utils/script_templates.py +1193 -0
- package/src/utils/sync_detection.py +887 -0
- package/src/utils/timeline_conform_live_probe.py +280 -0
- package/src/utils/timeline_kernel_live_probe.py +1091 -0
- package/src/utils/timeline_kernel_probe.py +185 -0
- package/src/utils/timeline_title_text.py +87 -0
- package/src/utils/update_check.py +610 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
"""Color page graph, LUT, and color-group tools."""
|
|
2
|
+
|
|
3
|
+
from src.granular.common import * # noqa: F401,F403
|
|
4
|
+
|
|
5
|
+
resolve = ResolveProxy()
|
|
6
|
+
|
|
7
|
+
@mcp.tool()
|
|
8
|
+
def graph_get_num_nodes(item_index: int = 0, track_type: str = "video", track_index: int = 1) -> Dict[str, Any]:
|
|
9
|
+
"""Get number of nodes in the color graph for a timeline item.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
item_index: 0-based item index. Default: 0.
|
|
13
|
+
track_type: 'video' or 'audio'. Default: 'video'.
|
|
14
|
+
track_index: 1-based track index. Default: 1.
|
|
15
|
+
"""
|
|
16
|
+
item, err = _get_timeline_item(track_type, track_index, item_index)
|
|
17
|
+
if err:
|
|
18
|
+
return err
|
|
19
|
+
graph = item.GetNodeGraph()
|
|
20
|
+
if not graph:
|
|
21
|
+
return {"error": "No node graph available"}
|
|
22
|
+
return {"num_nodes": graph.GetNumNodes()}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@mcp.tool()
|
|
26
|
+
def graph_set_lut(node_index: int, lut_path: str, item_index: int = 0, track_type: str = "video", track_index: int = 1) -> Dict[str, Any]:
|
|
27
|
+
"""Set LUT on a node in the color graph.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
node_index: 1-based node index.
|
|
31
|
+
lut_path: Absolute or relative LUT path.
|
|
32
|
+
item_index: 0-based timeline item index. Default: 0.
|
|
33
|
+
track_type: 'video' or 'audio'. Default: 'video'.
|
|
34
|
+
track_index: 1-based track index. Default: 1.
|
|
35
|
+
"""
|
|
36
|
+
item, err = _get_timeline_item(track_type, track_index, item_index)
|
|
37
|
+
if err:
|
|
38
|
+
return err
|
|
39
|
+
graph = item.GetNodeGraph()
|
|
40
|
+
if not graph:
|
|
41
|
+
return {"error": "No node graph available"}
|
|
42
|
+
result = graph.SetLUT(node_index, lut_path)
|
|
43
|
+
return {"success": bool(result)}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@mcp.tool()
|
|
47
|
+
def graph_get_lut(node_index: int, item_index: int = 0, track_type: str = "video", track_index: int = 1) -> Dict[str, Any]:
|
|
48
|
+
"""Get LUT path on a node in the color graph.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
node_index: 1-based node index.
|
|
52
|
+
item_index: 0-based timeline item index. Default: 0.
|
|
53
|
+
track_type: 'video' or 'audio'. Default: 'video'.
|
|
54
|
+
track_index: 1-based track index. Default: 1.
|
|
55
|
+
"""
|
|
56
|
+
item, err = _get_timeline_item(track_type, track_index, item_index)
|
|
57
|
+
if err:
|
|
58
|
+
return err
|
|
59
|
+
graph = item.GetNodeGraph()
|
|
60
|
+
if not graph:
|
|
61
|
+
return {"error": "No node graph available"}
|
|
62
|
+
lut = graph.GetLUT(node_index)
|
|
63
|
+
return {"node_index": node_index, "lut_path": lut if lut else ""}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@mcp.tool()
|
|
67
|
+
def graph_set_node_cache_mode(node_index: int, cache_value: int, item_index: int = 0, track_type: str = "video", track_index: int = 1) -> Dict[str, Any]:
|
|
68
|
+
"""Set the cache mode on a node.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
node_index: 1-based node index.
|
|
72
|
+
cache_value: -1=Auto, 0=Disabled, 1=Enabled.
|
|
73
|
+
item_index: 0-based timeline item index. Default: 0.
|
|
74
|
+
track_type: 'video' or 'audio'. Default: 'video'.
|
|
75
|
+
track_index: 1-based track index. Default: 1.
|
|
76
|
+
"""
|
|
77
|
+
item, err = _get_timeline_item(track_type, track_index, item_index)
|
|
78
|
+
if err:
|
|
79
|
+
return err
|
|
80
|
+
graph = item.GetNodeGraph()
|
|
81
|
+
if not graph:
|
|
82
|
+
return {"error": "No node graph available"}
|
|
83
|
+
result = graph.SetNodeCacheMode(node_index, cache_value)
|
|
84
|
+
return {"success": bool(result)}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@mcp.tool()
|
|
88
|
+
def graph_get_node_cache_mode(node_index: int, item_index: int = 0, track_type: str = "video", track_index: int = 1) -> Dict[str, Any]:
|
|
89
|
+
"""Get the cache mode of a node.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
node_index: 1-based node index.
|
|
93
|
+
item_index: 0-based timeline item index. Default: 0.
|
|
94
|
+
track_type: 'video' or 'audio'. Default: 'video'.
|
|
95
|
+
track_index: 1-based track index. Default: 1.
|
|
96
|
+
"""
|
|
97
|
+
item, err = _get_timeline_item(track_type, track_index, item_index)
|
|
98
|
+
if err:
|
|
99
|
+
return err
|
|
100
|
+
graph = item.GetNodeGraph()
|
|
101
|
+
if not graph:
|
|
102
|
+
return {"error": "No node graph available"}
|
|
103
|
+
mode = graph.GetNodeCacheMode(node_index)
|
|
104
|
+
modes = {-1: "Auto", 0: "Disabled", 1: "Enabled"}
|
|
105
|
+
return {"node_index": node_index, "cache_mode": mode, "mode_name": modes.get(mode, "Unknown")}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@mcp.tool()
|
|
109
|
+
def graph_get_node_label(node_index: int, item_index: int = 0, track_type: str = "video", track_index: int = 1) -> Dict[str, Any]:
|
|
110
|
+
"""Get the label of a node.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
node_index: 1-based node index.
|
|
114
|
+
item_index: 0-based timeline item index. Default: 0.
|
|
115
|
+
track_type: 'video' or 'audio'. Default: 'video'.
|
|
116
|
+
track_index: 1-based track index. Default: 1.
|
|
117
|
+
"""
|
|
118
|
+
item, err = _get_timeline_item(track_type, track_index, item_index)
|
|
119
|
+
if err:
|
|
120
|
+
return err
|
|
121
|
+
graph = item.GetNodeGraph()
|
|
122
|
+
if not graph:
|
|
123
|
+
return {"error": "No node graph available"}
|
|
124
|
+
label = graph.GetNodeLabel(node_index)
|
|
125
|
+
return {"node_index": node_index, "label": label if label else ""}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@mcp.tool()
|
|
129
|
+
def graph_get_tools_in_node(node_index: int, item_index: int = 0, track_type: str = "video", track_index: int = 1) -> Dict[str, Any]:
|
|
130
|
+
"""Get list of tools used in a node.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
node_index: 1-based node index.
|
|
134
|
+
item_index: 0-based timeline item index. Default: 0.
|
|
135
|
+
track_type: 'video' or 'audio'. Default: 'video'.
|
|
136
|
+
track_index: 1-based track index. Default: 1.
|
|
137
|
+
"""
|
|
138
|
+
item, err = _get_timeline_item(track_type, track_index, item_index)
|
|
139
|
+
if err:
|
|
140
|
+
return err
|
|
141
|
+
graph = item.GetNodeGraph()
|
|
142
|
+
if not graph:
|
|
143
|
+
return {"error": "No node graph available"}
|
|
144
|
+
tools = graph.GetToolsInNode(node_index)
|
|
145
|
+
return {"node_index": node_index, "tools": tools if tools else []}
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@mcp.tool()
|
|
149
|
+
def graph_set_node_enabled(node_index: int, is_enabled: bool, item_index: int = 0, track_type: str = "video", track_index: int = 1) -> Dict[str, Any]:
|
|
150
|
+
"""Enable or disable a node.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
node_index: 1-based node index.
|
|
154
|
+
is_enabled: True to enable, False to disable.
|
|
155
|
+
item_index: 0-based timeline item index. Default: 0.
|
|
156
|
+
track_type: 'video' or 'audio'. Default: 'video'.
|
|
157
|
+
track_index: 1-based track index. Default: 1.
|
|
158
|
+
"""
|
|
159
|
+
item, err = _get_timeline_item(track_type, track_index, item_index)
|
|
160
|
+
if err:
|
|
161
|
+
return err
|
|
162
|
+
graph = item.GetNodeGraph()
|
|
163
|
+
if not graph:
|
|
164
|
+
return {"error": "No node graph available"}
|
|
165
|
+
result = graph.SetNodeEnabled(node_index, is_enabled)
|
|
166
|
+
return {"success": bool(result)}
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@mcp.tool()
|
|
170
|
+
def graph_apply_grade_from_drx(drx_path: str, grade_mode: int = 0, item_index: int = 0, track_type: str = "video", track_index: int = 1) -> Dict[str, Any]:
|
|
171
|
+
"""Apply a grade from a .drx file to a timeline item's graph.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
drx_path: Absolute path to the .drx file.
|
|
175
|
+
grade_mode: 0=No keyframes, 1=Source Timecode aligned, 2=Start Frames aligned.
|
|
176
|
+
item_index: 0-based timeline item index. Default: 0.
|
|
177
|
+
track_type: 'video' or 'audio'. Default: 'video'.
|
|
178
|
+
track_index: 1-based track index. Default: 1.
|
|
179
|
+
"""
|
|
180
|
+
item, err = _get_timeline_item(track_type, track_index, item_index)
|
|
181
|
+
if err:
|
|
182
|
+
return err
|
|
183
|
+
graph = item.GetNodeGraph()
|
|
184
|
+
if not graph:
|
|
185
|
+
return {"error": "No node graph available"}
|
|
186
|
+
result = graph.ApplyGradeFromDRX(drx_path, grade_mode)
|
|
187
|
+
return {"success": bool(result)}
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@mcp.tool()
|
|
191
|
+
def graph_apply_arri_cdl_lut(item_index: int = 0, track_type: str = "video", track_index: int = 1) -> Dict[str, Any]:
|
|
192
|
+
"""Apply ARRI CDL and LUT to a timeline item's graph.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
item_index: 0-based timeline item index. Default: 0.
|
|
196
|
+
track_type: 'video' or 'audio'. Default: 'video'.
|
|
197
|
+
track_index: 1-based track index. Default: 1.
|
|
198
|
+
"""
|
|
199
|
+
item, err = _get_timeline_item(track_type, track_index, item_index)
|
|
200
|
+
if err:
|
|
201
|
+
return err
|
|
202
|
+
graph = item.GetNodeGraph()
|
|
203
|
+
if not graph:
|
|
204
|
+
return {"error": "No node graph available"}
|
|
205
|
+
result = graph.ApplyArriCdlLut()
|
|
206
|
+
return {"success": bool(result)}
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@mcp.tool()
|
|
210
|
+
def graph_reset_all_grades(item_index: int = 0, track_type: str = "video", track_index: int = 1) -> Dict[str, Any]:
|
|
211
|
+
"""Reset all grades on a timeline item's graph.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
item_index: 0-based timeline item index. Default: 0.
|
|
215
|
+
track_type: 'video' or 'audio'. Default: 'video'.
|
|
216
|
+
track_index: 1-based track index. Default: 1.
|
|
217
|
+
"""
|
|
218
|
+
item, err = _get_timeline_item(track_type, track_index, item_index)
|
|
219
|
+
if err:
|
|
220
|
+
return err
|
|
221
|
+
graph = item.GetNodeGraph()
|
|
222
|
+
if not graph:
|
|
223
|
+
return {"error": "No node graph available"}
|
|
224
|
+
result = graph.ResetAllGrades()
|
|
225
|
+
return {"success": bool(result)}
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
@mcp.tool()
|
|
229
|
+
def get_color_group_clips(group_name: str) -> Dict[str, Any]:
|
|
230
|
+
"""Get clips in a color group for the current timeline.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
group_name: Name of the color group.
|
|
234
|
+
"""
|
|
235
|
+
resolve = get_resolve()
|
|
236
|
+
if resolve is None:
|
|
237
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
238
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
239
|
+
if not project:
|
|
240
|
+
return {"error": "No project open"}
|
|
241
|
+
groups = project.GetColorGroupsList()
|
|
242
|
+
target = None
|
|
243
|
+
if groups:
|
|
244
|
+
for g in groups:
|
|
245
|
+
if g.GetName() == group_name:
|
|
246
|
+
target = g
|
|
247
|
+
break
|
|
248
|
+
if not target:
|
|
249
|
+
return {"error": f"Color group '{group_name}' not found"}
|
|
250
|
+
clips = target.GetClipsInTimeline()
|
|
251
|
+
if clips:
|
|
252
|
+
return {"group": group_name, "clips": [{"name": c.GetName()} for c in clips]}
|
|
253
|
+
return {"group": group_name, "clips": []}
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
@mcp.tool()
|
|
257
|
+
def get_color_group_pre_clip_node_graph(group_name: str) -> Dict[str, Any]:
|
|
258
|
+
"""Get the pre-clip node graph for a color group.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
group_name: Name of the color group.
|
|
262
|
+
"""
|
|
263
|
+
resolve = get_resolve()
|
|
264
|
+
if resolve is None:
|
|
265
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
266
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
267
|
+
if not project:
|
|
268
|
+
return {"error": "No project open"}
|
|
269
|
+
groups = project.GetColorGroupsList()
|
|
270
|
+
target = None
|
|
271
|
+
if groups:
|
|
272
|
+
for g in groups:
|
|
273
|
+
if g.GetName() == group_name:
|
|
274
|
+
target = g
|
|
275
|
+
break
|
|
276
|
+
if not target:
|
|
277
|
+
return {"error": f"Color group '{group_name}' not found"}
|
|
278
|
+
graph = target.GetPreClipNodeGraph()
|
|
279
|
+
if graph:
|
|
280
|
+
return {"group": group_name, "graph_type": "pre_clip", "num_nodes": graph.GetNumNodes()}
|
|
281
|
+
return {"error": "No pre-clip node graph available"}
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
@mcp.tool()
|
|
285
|
+
def get_color_group_post_clip_node_graph(group_name: str) -> Dict[str, Any]:
|
|
286
|
+
"""Get the post-clip node graph for a color group.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
group_name: Name of the color group.
|
|
290
|
+
"""
|
|
291
|
+
resolve = get_resolve()
|
|
292
|
+
if resolve is None:
|
|
293
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
294
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
295
|
+
if not project:
|
|
296
|
+
return {"error": "No project open"}
|
|
297
|
+
groups = project.GetColorGroupsList()
|
|
298
|
+
target = None
|
|
299
|
+
if groups:
|
|
300
|
+
for g in groups:
|
|
301
|
+
if g.GetName() == group_name:
|
|
302
|
+
target = g
|
|
303
|
+
break
|
|
304
|
+
if not target:
|
|
305
|
+
return {"error": f"Color group '{group_name}' not found"}
|
|
306
|
+
graph = target.GetPostClipNodeGraph()
|
|
307
|
+
if graph:
|
|
308
|
+
return {"group": group_name, "graph_type": "post_clip", "num_nodes": graph.GetNumNodes()}
|
|
309
|
+
return {"error": "No post-clip node graph available"}
|