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,1594 @@
|
|
|
1
|
+
"""Project, render, cache, cloud, and project-property tools."""
|
|
2
|
+
|
|
3
|
+
from src.granular.common import * # noqa: F401,F403
|
|
4
|
+
|
|
5
|
+
resolve = ResolveProxy()
|
|
6
|
+
|
|
7
|
+
@mcp.resource("resolve://projects")
|
|
8
|
+
def list_projects() -> List[str]:
|
|
9
|
+
"""List all available projects in the current database."""
|
|
10
|
+
project_manager = get_project_manager()
|
|
11
|
+
if not project_manager:
|
|
12
|
+
return ["Error: Failed to get Project Manager"]
|
|
13
|
+
|
|
14
|
+
projects = project_manager.GetProjectListInCurrentFolder()
|
|
15
|
+
|
|
16
|
+
# Filter out any empty strings that might be in the list
|
|
17
|
+
return [p for p in projects if p]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@mcp.resource("resolve://current-project")
|
|
21
|
+
def get_current_project_name() -> str:
|
|
22
|
+
"""Get the name of the currently open project."""
|
|
23
|
+
pm, current_project = get_current_project()
|
|
24
|
+
if not current_project:
|
|
25
|
+
return "No project currently open"
|
|
26
|
+
|
|
27
|
+
return current_project.GetName()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@mcp.resource("resolve://project-settings")
|
|
31
|
+
def get_project_settings() -> Dict[str, Any]:
|
|
32
|
+
"""Get all project settings from the current project."""
|
|
33
|
+
pm, current_project = get_current_project()
|
|
34
|
+
if not current_project:
|
|
35
|
+
return {"error": "No project currently open"}
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
# Get all settings
|
|
39
|
+
return current_project.GetSetting('')
|
|
40
|
+
except Exception as e:
|
|
41
|
+
return {"error": f"Failed to get project settings: {str(e)}"}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@mcp.resource("resolve://project-setting/{setting_name}")
|
|
45
|
+
def get_project_setting(setting_name: str) -> Dict[str, Any]:
|
|
46
|
+
"""Get a specific project setting by name.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
setting_name: The specific setting to retrieve.
|
|
50
|
+
"""
|
|
51
|
+
pm, current_project = get_current_project()
|
|
52
|
+
if not current_project:
|
|
53
|
+
return {"error": "No project currently open"}
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
# Get specific setting
|
|
57
|
+
value = current_project.GetSetting(setting_name)
|
|
58
|
+
return {setting_name: value}
|
|
59
|
+
except Exception as e:
|
|
60
|
+
return {"error": f"Failed to get project setting '{setting_name}': {str(e)}"}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@mcp.tool(annotations=DESTRUCTIVE_TOOL)
|
|
64
|
+
def set_project_setting(setting_name: str, setting_value: Any) -> str:
|
|
65
|
+
"""Set a project setting to the specified value.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
setting_name: The name of the setting to change
|
|
69
|
+
setting_value: The new value for the setting (can be string, integer, float, or boolean)
|
|
70
|
+
"""
|
|
71
|
+
pm, current_project = get_current_project()
|
|
72
|
+
if not current_project:
|
|
73
|
+
return "Error: No project currently open"
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
# Convert setting_value to string if it's not already
|
|
77
|
+
if not isinstance(setting_value, str):
|
|
78
|
+
setting_value = str(setting_value)
|
|
79
|
+
|
|
80
|
+
# Try to determine if this should be a numeric value
|
|
81
|
+
# DaVinci Resolve sometimes expects numeric values for certain settings
|
|
82
|
+
try:
|
|
83
|
+
# Check if it's a number in string form
|
|
84
|
+
if setting_value.isdigit() or (setting_value.startswith('-') and setting_value[1:].isdigit()):
|
|
85
|
+
# It's an integer
|
|
86
|
+
numeric_value = int(setting_value)
|
|
87
|
+
# Try with numeric value first
|
|
88
|
+
if current_project.SetSetting(setting_name, numeric_value):
|
|
89
|
+
return f"Successfully set project setting '{setting_name}' to numeric value {numeric_value}"
|
|
90
|
+
elif '.' in setting_value and setting_value.replace('.', '', 1).replace('-', '', 1).isdigit():
|
|
91
|
+
# It's a float
|
|
92
|
+
numeric_value = float(setting_value)
|
|
93
|
+
# Try with float value
|
|
94
|
+
if current_project.SetSetting(setting_name, numeric_value):
|
|
95
|
+
return f"Successfully set project setting '{setting_name}' to numeric value {numeric_value}"
|
|
96
|
+
except (ValueError, TypeError):
|
|
97
|
+
# Not a number or conversion failed, continue with string value
|
|
98
|
+
pass
|
|
99
|
+
|
|
100
|
+
# Fall back to string value if numeric didn't work or wasn't applicable
|
|
101
|
+
result = current_project.SetSetting(setting_name, setting_value)
|
|
102
|
+
if result:
|
|
103
|
+
return f"Successfully set project setting '{setting_name}' to '{setting_value}'"
|
|
104
|
+
else:
|
|
105
|
+
return f"Failed to set project setting '{setting_name}'"
|
|
106
|
+
except Exception as e:
|
|
107
|
+
return f"Error setting project setting: {str(e)}"
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@mcp.tool()
|
|
111
|
+
def open_project(name: str) -> str:
|
|
112
|
+
"""Open a project by name.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
name: The name of the project to open
|
|
116
|
+
"""
|
|
117
|
+
resolve = get_resolve()
|
|
118
|
+
if resolve is None:
|
|
119
|
+
return "Error: Not connected to DaVinci Resolve"
|
|
120
|
+
|
|
121
|
+
if not name:
|
|
122
|
+
return "Error: Project name cannot be empty"
|
|
123
|
+
|
|
124
|
+
project_manager = resolve.GetProjectManager()
|
|
125
|
+
if not project_manager:
|
|
126
|
+
return "Error: Failed to get Project Manager"
|
|
127
|
+
|
|
128
|
+
# Check if project exists
|
|
129
|
+
projects = project_manager.GetProjectListInCurrentFolder()
|
|
130
|
+
if name not in projects:
|
|
131
|
+
return f"Error: Project '{name}' not found. Available projects: {', '.join(projects)}"
|
|
132
|
+
|
|
133
|
+
result = project_manager.LoadProject(name)
|
|
134
|
+
if result:
|
|
135
|
+
return f"Successfully opened project '{name}'"
|
|
136
|
+
else:
|
|
137
|
+
return f"Failed to open project '{name}'"
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@mcp.tool()
|
|
141
|
+
def create_project(name: str, media_location_path: str = None) -> str:
|
|
142
|
+
"""Create a new project with the given name.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
name: The name for the new project
|
|
146
|
+
media_location_path: Optional project media location path (Resolve 20.2.2+).
|
|
147
|
+
"""
|
|
148
|
+
resolve = get_resolve()
|
|
149
|
+
if resolve is None:
|
|
150
|
+
return "Error: Not connected to DaVinci Resolve"
|
|
151
|
+
|
|
152
|
+
if not name:
|
|
153
|
+
return "Error: Project name cannot be empty"
|
|
154
|
+
|
|
155
|
+
project_manager = resolve.GetProjectManager()
|
|
156
|
+
if not project_manager:
|
|
157
|
+
return "Error: Failed to get Project Manager"
|
|
158
|
+
|
|
159
|
+
# Check if project already exists
|
|
160
|
+
projects = project_manager.GetProjectListInCurrentFolder()
|
|
161
|
+
if name in projects:
|
|
162
|
+
return f"Error: Project '{name}' already exists"
|
|
163
|
+
|
|
164
|
+
if media_location_path:
|
|
165
|
+
version = resolve.GetVersion() or [0]
|
|
166
|
+
if version[0] < 20 or (version[0] == 20 and len(version) > 2 and (version[1], version[2]) < (2, 2)):
|
|
167
|
+
return "Error: ProjectManager.CreateProject media_location_path requires DaVinci Resolve 20.2.2+"
|
|
168
|
+
result = project_manager.CreateProject(name, media_location_path)
|
|
169
|
+
else:
|
|
170
|
+
result = project_manager.CreateProject(name)
|
|
171
|
+
if result:
|
|
172
|
+
return f"Successfully created project '{name}'"
|
|
173
|
+
else:
|
|
174
|
+
return f"Failed to create project '{name}'"
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@mcp.tool()
|
|
178
|
+
def save_project() -> str:
|
|
179
|
+
"""Save the current project.
|
|
180
|
+
|
|
181
|
+
Note that DaVinci Resolve typically auto-saves projects, so this may not be necessary.
|
|
182
|
+
"""
|
|
183
|
+
pm, current_project = get_current_project()
|
|
184
|
+
if not current_project:
|
|
185
|
+
return "Error: No project currently open"
|
|
186
|
+
|
|
187
|
+
project_name = current_project.GetName()
|
|
188
|
+
success = False
|
|
189
|
+
error_message = None
|
|
190
|
+
|
|
191
|
+
# Try multiple approaches to save the project
|
|
192
|
+
try:
|
|
193
|
+
# Method 1: Try direct save method if available
|
|
194
|
+
try:
|
|
195
|
+
if hasattr(current_project, "SaveProject"):
|
|
196
|
+
result = current_project.SaveProject()
|
|
197
|
+
if result:
|
|
198
|
+
logger.info(f"Project '{project_name}' saved using SaveProject method")
|
|
199
|
+
success = True
|
|
200
|
+
except Exception as e:
|
|
201
|
+
logger.error(f"Error in SaveProject method: {str(e)}")
|
|
202
|
+
error_message = str(e)
|
|
203
|
+
|
|
204
|
+
# Method 2: Try project manager save method
|
|
205
|
+
if not success:
|
|
206
|
+
try:
|
|
207
|
+
if hasattr(project_manager, "SaveProject"):
|
|
208
|
+
result = project_manager.SaveProject()
|
|
209
|
+
if result:
|
|
210
|
+
logger.info(f"Project '{project_name}' saved using ProjectManager.SaveProject method")
|
|
211
|
+
success = True
|
|
212
|
+
except Exception as e:
|
|
213
|
+
logger.error(f"Error in ProjectManager.SaveProject method: {str(e)}")
|
|
214
|
+
if not error_message:
|
|
215
|
+
error_message = str(e)
|
|
216
|
+
|
|
217
|
+
# Method 3: Try the export method as a backup approach
|
|
218
|
+
if not success:
|
|
219
|
+
try:
|
|
220
|
+
# Get a temporary file path that Resolve can access
|
|
221
|
+
temp_dir = _resolve_safe_dir(tempfile.gettempdir())
|
|
222
|
+
os.makedirs(temp_dir, exist_ok=True)
|
|
223
|
+
temp_file = os.path.join(temp_dir, f"{project_name}_temp.drp")
|
|
224
|
+
|
|
225
|
+
# Try to export the project, which should trigger a save
|
|
226
|
+
result = project_manager.ExportProject(project_name, temp_file)
|
|
227
|
+
if result:
|
|
228
|
+
logger.info(f"Project '{project_name}' saved via temporary export to {temp_file}")
|
|
229
|
+
# Try to clean up temp file
|
|
230
|
+
try:
|
|
231
|
+
if os.path.exists(temp_file):
|
|
232
|
+
os.remove(temp_file)
|
|
233
|
+
except OSError:
|
|
234
|
+
logger.debug("Could not remove temporary project export %s", temp_file, exc_info=True)
|
|
235
|
+
success = True
|
|
236
|
+
except Exception as e:
|
|
237
|
+
logger.error(f"Error in export method: {str(e)}")
|
|
238
|
+
if not error_message:
|
|
239
|
+
error_message = str(e)
|
|
240
|
+
|
|
241
|
+
# If all else fails, rely on auto-save
|
|
242
|
+
if not success:
|
|
243
|
+
return f"Automatic save likely in effect for project '{project_name}'. Manual save attempts failed: {error_message if error_message else 'Unknown error'}"
|
|
244
|
+
else:
|
|
245
|
+
return f"Successfully saved project '{project_name}'"
|
|
246
|
+
|
|
247
|
+
except Exception as e:
|
|
248
|
+
logger.error(f"Error saving project: {str(e)}")
|
|
249
|
+
return f"Error saving project: {str(e)}"
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
@mcp.tool()
|
|
253
|
+
def close_project() -> str:
|
|
254
|
+
"""Close the current project.
|
|
255
|
+
|
|
256
|
+
This closes the current project without saving. If you need to save, use the save_project function first.
|
|
257
|
+
"""
|
|
258
|
+
pm, current_project = get_current_project()
|
|
259
|
+
if not current_project:
|
|
260
|
+
return "Error: No project currently open"
|
|
261
|
+
|
|
262
|
+
project_name = current_project.GetName()
|
|
263
|
+
|
|
264
|
+
# Close the project
|
|
265
|
+
try:
|
|
266
|
+
result = project_manager.CloseProject(current_project)
|
|
267
|
+
if result:
|
|
268
|
+
logger.info(f"Project '{project_name}' closed successfully")
|
|
269
|
+
return f"Successfully closed project '{project_name}'"
|
|
270
|
+
else:
|
|
271
|
+
logger.error(f"Failed to close project '{project_name}'")
|
|
272
|
+
return f"Failed to close project '{project_name}'"
|
|
273
|
+
except Exception as e:
|
|
274
|
+
logger.error(f"Error closing project: {str(e)}")
|
|
275
|
+
return f"Error closing project: {str(e)}"
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
@mcp.resource("resolve://cache/settings")
|
|
279
|
+
def get_cache_settings() -> Dict[str, Any]:
|
|
280
|
+
"""Get current cache settings from the project."""
|
|
281
|
+
pm, current_project = get_current_project()
|
|
282
|
+
if not current_project:
|
|
283
|
+
return {"error": "No project currently open"}
|
|
284
|
+
|
|
285
|
+
try:
|
|
286
|
+
# Get all cache-related settings
|
|
287
|
+
settings = {}
|
|
288
|
+
cache_keys = [
|
|
289
|
+
"CacheMode",
|
|
290
|
+
"CacheClipMode",
|
|
291
|
+
"OptimizedMediaMode",
|
|
292
|
+
"ProxyMode",
|
|
293
|
+
"ProxyQuality",
|
|
294
|
+
"TimelineCacheMode",
|
|
295
|
+
"LocalCachePath",
|
|
296
|
+
"NetworkCachePath"
|
|
297
|
+
]
|
|
298
|
+
|
|
299
|
+
for key in cache_keys:
|
|
300
|
+
value = current_project.GetSetting(key)
|
|
301
|
+
settings[key] = value
|
|
302
|
+
|
|
303
|
+
return settings
|
|
304
|
+
except Exception as e:
|
|
305
|
+
return {"error": f"Failed to get cache settings: {str(e)}"}
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
@mcp.tool()
|
|
309
|
+
def set_cache_mode(mode: str) -> str:
|
|
310
|
+
"""Set cache mode for the current project.
|
|
311
|
+
|
|
312
|
+
Args:
|
|
313
|
+
mode: Cache mode to set. Options: 'auto', 'on', 'off'
|
|
314
|
+
"""
|
|
315
|
+
pm, current_project = get_current_project()
|
|
316
|
+
if not current_project:
|
|
317
|
+
return "Error: No project currently open"
|
|
318
|
+
|
|
319
|
+
# Validate mode
|
|
320
|
+
valid_modes = ["auto", "on", "off"]
|
|
321
|
+
mode = mode.lower()
|
|
322
|
+
if mode not in valid_modes:
|
|
323
|
+
return f"Error: Invalid cache mode. Must be one of: {', '.join(valid_modes)}"
|
|
324
|
+
|
|
325
|
+
# Convert mode to API value
|
|
326
|
+
mode_map = {
|
|
327
|
+
"auto": "0",
|
|
328
|
+
"on": "1",
|
|
329
|
+
"off": "2"
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
try:
|
|
333
|
+
result = current_project.SetSetting("CacheMode", mode_map[mode])
|
|
334
|
+
if result:
|
|
335
|
+
return f"Successfully set cache mode to '{mode}'"
|
|
336
|
+
else:
|
|
337
|
+
return f"Failed to set cache mode to '{mode}'"
|
|
338
|
+
except Exception as e:
|
|
339
|
+
return f"Error setting cache mode: {str(e)}"
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
@mcp.tool()
|
|
343
|
+
def set_optimized_media_mode(mode: str) -> str:
|
|
344
|
+
"""Set optimized media mode for the current project.
|
|
345
|
+
|
|
346
|
+
Args:
|
|
347
|
+
mode: Optimized media mode to set. Options: 'auto', 'on', 'off'
|
|
348
|
+
"""
|
|
349
|
+
pm, current_project = get_current_project()
|
|
350
|
+
if not current_project:
|
|
351
|
+
return "Error: No project currently open"
|
|
352
|
+
|
|
353
|
+
# Validate mode
|
|
354
|
+
valid_modes = ["auto", "on", "off"]
|
|
355
|
+
mode = mode.lower()
|
|
356
|
+
if mode not in valid_modes:
|
|
357
|
+
return f"Error: Invalid optimized media mode. Must be one of: {', '.join(valid_modes)}"
|
|
358
|
+
|
|
359
|
+
# Convert mode to API value
|
|
360
|
+
mode_map = {
|
|
361
|
+
"auto": "0",
|
|
362
|
+
"on": "1",
|
|
363
|
+
"off": "2"
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
try:
|
|
367
|
+
result = current_project.SetSetting("OptimizedMediaMode", mode_map[mode])
|
|
368
|
+
if result:
|
|
369
|
+
return f"Successfully set optimized media mode to '{mode}'"
|
|
370
|
+
else:
|
|
371
|
+
return f"Failed to set optimized media mode to '{mode}'"
|
|
372
|
+
except Exception as e:
|
|
373
|
+
return f"Error setting optimized media mode: {str(e)}"
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
@mcp.tool()
|
|
377
|
+
def set_proxy_mode(mode: str) -> str:
|
|
378
|
+
"""Set proxy media mode for the current project.
|
|
379
|
+
|
|
380
|
+
Args:
|
|
381
|
+
mode: Proxy mode to set. Options: 'auto', 'on', 'off'
|
|
382
|
+
"""
|
|
383
|
+
pm, current_project = get_current_project()
|
|
384
|
+
if not current_project:
|
|
385
|
+
return "Error: No project currently open"
|
|
386
|
+
|
|
387
|
+
# Validate mode
|
|
388
|
+
valid_modes = ["auto", "on", "off"]
|
|
389
|
+
mode = mode.lower()
|
|
390
|
+
if mode not in valid_modes:
|
|
391
|
+
return f"Error: Invalid proxy mode. Must be one of: {', '.join(valid_modes)}"
|
|
392
|
+
|
|
393
|
+
# Convert mode to API value
|
|
394
|
+
mode_map = {
|
|
395
|
+
"auto": "0",
|
|
396
|
+
"on": "1",
|
|
397
|
+
"off": "2"
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
try:
|
|
401
|
+
result = current_project.SetSetting("ProxyMode", mode_map[mode])
|
|
402
|
+
if result:
|
|
403
|
+
return f"Successfully set proxy mode to '{mode}'"
|
|
404
|
+
else:
|
|
405
|
+
return f"Failed to set proxy mode to '{mode}'"
|
|
406
|
+
except Exception as e:
|
|
407
|
+
return f"Error setting proxy mode: {str(e)}"
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
@mcp.tool()
|
|
411
|
+
def set_proxy_quality(quality: str) -> str:
|
|
412
|
+
"""Set proxy media quality for the current project.
|
|
413
|
+
|
|
414
|
+
Args:
|
|
415
|
+
quality: Proxy quality to set. Options: 'quarter', 'half', 'threeQuarter', 'full'
|
|
416
|
+
"""
|
|
417
|
+
pm, current_project = get_current_project()
|
|
418
|
+
if not current_project:
|
|
419
|
+
return "Error: No project currently open"
|
|
420
|
+
|
|
421
|
+
# Validate quality
|
|
422
|
+
valid_qualities = ["quarter", "half", "threeQuarter", "full"]
|
|
423
|
+
if quality not in valid_qualities:
|
|
424
|
+
return f"Error: Invalid proxy quality. Must be one of: {', '.join(valid_qualities)}"
|
|
425
|
+
|
|
426
|
+
# Convert quality to API value
|
|
427
|
+
quality_map = {
|
|
428
|
+
"quarter": "0",
|
|
429
|
+
"half": "1",
|
|
430
|
+
"threeQuarter": "2",
|
|
431
|
+
"full": "3"
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
try:
|
|
435
|
+
result = current_project.SetSetting("ProxyQuality", quality_map[quality])
|
|
436
|
+
if result:
|
|
437
|
+
return f"Successfully set proxy quality to '{quality}'"
|
|
438
|
+
else:
|
|
439
|
+
return f"Failed to set proxy quality to '{quality}'"
|
|
440
|
+
except Exception as e:
|
|
441
|
+
return f"Error setting proxy quality: {str(e)}"
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
@mcp.tool()
|
|
445
|
+
def set_cache_path(path_type: str, path: str) -> str:
|
|
446
|
+
"""Set cache file path for the current project.
|
|
447
|
+
|
|
448
|
+
Args:
|
|
449
|
+
path_type: Type of cache path to set. Options: 'local', 'network'
|
|
450
|
+
path: File system path for the cache
|
|
451
|
+
"""
|
|
452
|
+
pm, current_project = get_current_project()
|
|
453
|
+
if not current_project:
|
|
454
|
+
return "Error: No project currently open"
|
|
455
|
+
|
|
456
|
+
# Validate path_type
|
|
457
|
+
valid_path_types = ["local", "network"]
|
|
458
|
+
path_type = path_type.lower()
|
|
459
|
+
if path_type not in valid_path_types:
|
|
460
|
+
return f"Error: Invalid path type. Must be one of: {', '.join(valid_path_types)}"
|
|
461
|
+
|
|
462
|
+
# Check if directory exists
|
|
463
|
+
if not os.path.exists(path):
|
|
464
|
+
return f"Error: Path '{path}' does not exist"
|
|
465
|
+
|
|
466
|
+
setting_key = "LocalCachePath" if path_type == "local" else "NetworkCachePath"
|
|
467
|
+
|
|
468
|
+
try:
|
|
469
|
+
result = current_project.SetSetting(setting_key, path)
|
|
470
|
+
if result:
|
|
471
|
+
return f"Successfully set {path_type} cache path to '{path}'"
|
|
472
|
+
else:
|
|
473
|
+
return f"Failed to set {path_type} cache path to '{path}'"
|
|
474
|
+
except Exception as e:
|
|
475
|
+
return f"Error setting cache path: {str(e)}"
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
@mcp.tool()
|
|
480
|
+
def create_cloud_project_tool(
|
|
481
|
+
project_name: Optional[str] = None,
|
|
482
|
+
project_media_path: Optional[str] = None,
|
|
483
|
+
is_collab: Optional[bool] = None,
|
|
484
|
+
sync_mode: Optional[str] = None,
|
|
485
|
+
is_camera_access: Optional[bool] = None,
|
|
486
|
+
) -> Dict[str, Any]:
|
|
487
|
+
"""Create a new cloud project.
|
|
488
|
+
|
|
489
|
+
Mirrors ProjectManager.CreateCloudProject({cloudSettings}) per docs lines 138, 576-594.
|
|
490
|
+
|
|
491
|
+
Args:
|
|
492
|
+
project_name: maps to resolve.CLOUD_SETTING_PROJECT_NAME (required for create).
|
|
493
|
+
project_media_path: maps to resolve.CLOUD_SETTING_PROJECT_MEDIA_PATH (required for create).
|
|
494
|
+
is_collab: maps to resolve.CLOUD_SETTING_IS_COLLAB.
|
|
495
|
+
sync_mode: 'none', 'proxy_only' (default), or 'proxy_and_orig' — maps to resolve.CLOUD_SYNC_*.
|
|
496
|
+
is_camera_access: maps to resolve.CLOUD_SETTING_IS_CAMERA_ACCESS.
|
|
497
|
+
"""
|
|
498
|
+
return create_cloud_project(get_resolve(), project_name=project_name,
|
|
499
|
+
project_media_path=project_media_path, is_collab=is_collab,
|
|
500
|
+
sync_mode=sync_mode, is_camera_access=is_camera_access)
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
@mcp.tool()
|
|
504
|
+
def load_cloud_project_tool(
|
|
505
|
+
project_name: Optional[str] = None,
|
|
506
|
+
project_media_path: Optional[str] = None,
|
|
507
|
+
sync_mode: Optional[str] = None,
|
|
508
|
+
) -> Dict[str, Any]:
|
|
509
|
+
"""Load a cloud project.
|
|
510
|
+
|
|
511
|
+
Mirrors ProjectManager.LoadCloudProject({cloudSettings}). Per docs line 585,
|
|
512
|
+
only project_name, project_media_path, and sync_mode are honoured.
|
|
513
|
+
|
|
514
|
+
Args:
|
|
515
|
+
project_name: required.
|
|
516
|
+
project_media_path: required.
|
|
517
|
+
sync_mode: 'none', 'proxy_only', or 'proxy_and_orig'.
|
|
518
|
+
"""
|
|
519
|
+
return load_cloud_project(get_resolve(), project_name=project_name,
|
|
520
|
+
project_media_path=project_media_path, sync_mode=sync_mode)
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
@mcp.tool()
|
|
524
|
+
def import_cloud_project_tool(
|
|
525
|
+
file_path: str,
|
|
526
|
+
project_name: Optional[str] = None,
|
|
527
|
+
project_media_path: Optional[str] = None,
|
|
528
|
+
is_collab: Optional[bool] = None,
|
|
529
|
+
sync_mode: Optional[str] = None,
|
|
530
|
+
is_camera_access: Optional[bool] = None,
|
|
531
|
+
) -> Dict[str, Any]:
|
|
532
|
+
"""Import a cloud project from a file.
|
|
533
|
+
|
|
534
|
+
Mirrors ProjectManager.ImportCloudProject(filePath, {cloudSettings}).
|
|
535
|
+
|
|
536
|
+
Args:
|
|
537
|
+
file_path: absolute path to the cloud project file (required).
|
|
538
|
+
project_name, project_media_path, is_collab, sync_mode, is_camera_access:
|
|
539
|
+
cloudSettings dict keys; same semantics as create_cloud_project_tool.
|
|
540
|
+
"""
|
|
541
|
+
return import_cloud_project(get_resolve(), file_path=file_path,
|
|
542
|
+
project_name=project_name, project_media_path=project_media_path,
|
|
543
|
+
is_collab=is_collab, sync_mode=sync_mode,
|
|
544
|
+
is_camera_access=is_camera_access)
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
@mcp.tool()
|
|
548
|
+
def restore_cloud_project_tool(
|
|
549
|
+
folder_path: str,
|
|
550
|
+
project_name: Optional[str] = None,
|
|
551
|
+
project_media_path: Optional[str] = None,
|
|
552
|
+
is_collab: Optional[bool] = None,
|
|
553
|
+
sync_mode: Optional[str] = None,
|
|
554
|
+
is_camera_access: Optional[bool] = None,
|
|
555
|
+
) -> Dict[str, Any]:
|
|
556
|
+
"""Restore a cloud project from a folder.
|
|
557
|
+
|
|
558
|
+
Mirrors ProjectManager.RestoreCloudProject(folderPath, {cloudSettings}).
|
|
559
|
+
|
|
560
|
+
Args:
|
|
561
|
+
folder_path: absolute path to the cloud project folder (required).
|
|
562
|
+
project_name, project_media_path, is_collab, sync_mode, is_camera_access:
|
|
563
|
+
cloudSettings dict keys; same semantics as create_cloud_project_tool.
|
|
564
|
+
"""
|
|
565
|
+
return restore_cloud_project(get_resolve(), folder_path=folder_path,
|
|
566
|
+
project_name=project_name, project_media_path=project_media_path,
|
|
567
|
+
is_collab=is_collab, sync_mode=sync_mode,
|
|
568
|
+
is_camera_access=is_camera_access)
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
@mcp.resource("resolve://project/properties")
|
|
572
|
+
def get_project_properties_endpoint() -> Dict[str, Any]:
|
|
573
|
+
"""Get all project properties for the current project."""
|
|
574
|
+
pm, current_project = get_current_project()
|
|
575
|
+
if not current_project:
|
|
576
|
+
return {"error": "No project currently open"}
|
|
577
|
+
|
|
578
|
+
return get_all_project_properties(current_project)
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
@mcp.resource("resolve://project/property/{property_name}")
|
|
582
|
+
def get_project_property_endpoint(property_name: str) -> Dict[str, Any]:
|
|
583
|
+
"""Get a specific project property value.
|
|
584
|
+
|
|
585
|
+
Args:
|
|
586
|
+
property_name: Name of the property to get
|
|
587
|
+
"""
|
|
588
|
+
pm, current_project = get_current_project()
|
|
589
|
+
if not current_project:
|
|
590
|
+
return {"error": "No project currently open"}
|
|
591
|
+
|
|
592
|
+
value = get_project_property(current_project, property_name)
|
|
593
|
+
return {property_name: value}
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
@mcp.tool()
|
|
597
|
+
def set_project_property_tool(property_name: str, property_value: Any) -> str:
|
|
598
|
+
"""Set a project property value.
|
|
599
|
+
|
|
600
|
+
Args:
|
|
601
|
+
property_name: Name of the property to set
|
|
602
|
+
property_value: Value to set for the property
|
|
603
|
+
"""
|
|
604
|
+
pm, current_project = get_current_project()
|
|
605
|
+
if not current_project:
|
|
606
|
+
return "Error: No project currently open"
|
|
607
|
+
|
|
608
|
+
result = set_project_property(current_project, property_name, property_value)
|
|
609
|
+
|
|
610
|
+
if result:
|
|
611
|
+
return f"Successfully set project property '{property_name}' to '{property_value}'"
|
|
612
|
+
else:
|
|
613
|
+
return f"Failed to set project property '{property_name}'"
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
@mcp.resource("resolve://project/timeline-format")
|
|
617
|
+
def get_timeline_format() -> Dict[str, Any]:
|
|
618
|
+
"""Get timeline format settings for the current project."""
|
|
619
|
+
pm, current_project = get_current_project()
|
|
620
|
+
if not current_project:
|
|
621
|
+
return {"error": "No project currently open"}
|
|
622
|
+
|
|
623
|
+
return get_timeline_format_settings(current_project)
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
@mcp.tool()
|
|
627
|
+
def set_timeline_format_tool(width: int, height: int, frame_rate: float, interlaced: bool = False) -> str:
|
|
628
|
+
"""Set timeline format (resolution and frame rate).
|
|
629
|
+
|
|
630
|
+
Args:
|
|
631
|
+
width: Timeline width in pixels
|
|
632
|
+
height: Timeline height in pixels
|
|
633
|
+
frame_rate: Timeline frame rate
|
|
634
|
+
interlaced: Whether the timeline should use interlaced processing
|
|
635
|
+
"""
|
|
636
|
+
pm, current_project = get_current_project()
|
|
637
|
+
if not current_project:
|
|
638
|
+
return "Error: No project currently open"
|
|
639
|
+
|
|
640
|
+
result = set_timeline_format(current_project, width, height, frame_rate, interlaced)
|
|
641
|
+
|
|
642
|
+
if result:
|
|
643
|
+
interlace_status = "interlaced" if interlaced else "progressive"
|
|
644
|
+
return f"Successfully set timeline format to {width}x{height} at {frame_rate} fps ({interlace_status})"
|
|
645
|
+
else:
|
|
646
|
+
return "Failed to set timeline format"
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
@mcp.resource("resolve://project/superscale")
|
|
650
|
+
def get_superscale_settings_endpoint() -> Dict[str, Any]:
|
|
651
|
+
"""Get SuperScale settings for the current project."""
|
|
652
|
+
pm, current_project = get_current_project()
|
|
653
|
+
if not current_project:
|
|
654
|
+
return {"error": "No project currently open"}
|
|
655
|
+
|
|
656
|
+
return get_superscale_settings(current_project)
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
@mcp.tool()
|
|
660
|
+
def set_superscale_settings_tool(enabled: bool, quality: int = 0) -> str:
|
|
661
|
+
"""Set SuperScale settings for the current project.
|
|
662
|
+
|
|
663
|
+
Args:
|
|
664
|
+
enabled: Whether SuperScale is enabled
|
|
665
|
+
quality: SuperScale quality (0=Auto, 1=Better Quality, 2=Smoother)
|
|
666
|
+
"""
|
|
667
|
+
pm, current_project = get_current_project()
|
|
668
|
+
if not current_project:
|
|
669
|
+
return "Error: No project currently open"
|
|
670
|
+
|
|
671
|
+
quality_names = {
|
|
672
|
+
0: "Auto",
|
|
673
|
+
1: "Better Quality",
|
|
674
|
+
2: "Smoother"
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
result = set_superscale_settings(current_project, enabled, quality)
|
|
678
|
+
|
|
679
|
+
if result:
|
|
680
|
+
status = "enabled" if enabled else "disabled"
|
|
681
|
+
quality_name = quality_names.get(quality, "Unknown")
|
|
682
|
+
return f"Successfully {status} SuperScale with quality set to {quality_name}"
|
|
683
|
+
else:
|
|
684
|
+
return "Failed to set SuperScale settings"
|
|
685
|
+
|
|
686
|
+
|
|
687
|
+
@mcp.resource("resolve://project/color-settings")
|
|
688
|
+
def get_color_settings_endpoint() -> Dict[str, Any]:
|
|
689
|
+
"""Get color science and color space settings for the current project."""
|
|
690
|
+
pm, current_project = get_current_project()
|
|
691
|
+
if not current_project:
|
|
692
|
+
return {"error": "No project currently open"}
|
|
693
|
+
|
|
694
|
+
return get_color_settings(current_project)
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
@mcp.tool()
|
|
698
|
+
def set_color_science_mode_tool(mode: str) -> str:
|
|
699
|
+
"""Set color science mode for the current project.
|
|
700
|
+
|
|
701
|
+
Args:
|
|
702
|
+
mode: Color science mode ('YRGB', 'YRGB Color Managed', 'ACEScct', or numeric value)
|
|
703
|
+
"""
|
|
704
|
+
pm, current_project = get_current_project()
|
|
705
|
+
if not current_project:
|
|
706
|
+
return "Error: No project currently open"
|
|
707
|
+
|
|
708
|
+
result = set_color_science_mode(current_project, mode)
|
|
709
|
+
|
|
710
|
+
if result:
|
|
711
|
+
return f"Successfully set color science mode to '{mode}'"
|
|
712
|
+
else:
|
|
713
|
+
return f"Failed to set color science mode to '{mode}'"
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
@mcp.tool()
|
|
717
|
+
def set_color_space_tool(color_space: str, gamma: str = None) -> str:
|
|
718
|
+
"""Set timeline color space and gamma.
|
|
719
|
+
|
|
720
|
+
Args:
|
|
721
|
+
color_space: Timeline color space (e.g., 'Rec.709', 'DCI-P3 D65', 'Rec.2020')
|
|
722
|
+
gamma: Timeline gamma (e.g., 'Rec.709 Gamma', 'Gamma 2.4')
|
|
723
|
+
"""
|
|
724
|
+
pm, current_project = get_current_project()
|
|
725
|
+
if not current_project:
|
|
726
|
+
return "Error: No project currently open"
|
|
727
|
+
|
|
728
|
+
result = set_color_space(current_project, color_space, gamma)
|
|
729
|
+
|
|
730
|
+
if result:
|
|
731
|
+
if gamma:
|
|
732
|
+
return f"Successfully set timeline color space to '{color_space}' with gamma '{gamma}'"
|
|
733
|
+
else:
|
|
734
|
+
return f"Successfully set timeline color space to '{color_space}'"
|
|
735
|
+
else:
|
|
736
|
+
return "Failed to set timeline color space"
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
@mcp.resource("resolve://project/metadata")
|
|
740
|
+
def get_project_metadata_endpoint() -> Dict[str, Any]:
|
|
741
|
+
"""Get metadata for the current project."""
|
|
742
|
+
pm, current_project = get_current_project()
|
|
743
|
+
if not current_project:
|
|
744
|
+
return {"error": "No project currently open"}
|
|
745
|
+
|
|
746
|
+
return get_project_metadata(current_project)
|
|
747
|
+
|
|
748
|
+
|
|
749
|
+
@mcp.resource("resolve://project/info")
|
|
750
|
+
def get_project_info_endpoint() -> Dict[str, Any]:
|
|
751
|
+
"""Get comprehensive information about the current project."""
|
|
752
|
+
pm, current_project = get_current_project()
|
|
753
|
+
if not current_project:
|
|
754
|
+
return {"error": "No project currently open"}
|
|
755
|
+
|
|
756
|
+
return get_project_info(current_project)
|
|
757
|
+
|
|
758
|
+
|
|
759
|
+
@mcp.tool()
|
|
760
|
+
def archive_project(project_name: str, archive_path: str, archive_src_media: bool = True, archive_render_cache: bool = True, archive_proxy_media: bool = False) -> Dict[str, Any]:
|
|
761
|
+
"""Archive a project to a file with optional media.
|
|
762
|
+
|
|
763
|
+
Args:
|
|
764
|
+
project_name: Name of the project to archive.
|
|
765
|
+
archive_path: Absolute path for the archive file (.dra).
|
|
766
|
+
archive_src_media: Include source media in archive. Default: True.
|
|
767
|
+
archive_render_cache: Include render cache. Default: True.
|
|
768
|
+
archive_proxy_media: Include proxy media. Default: False.
|
|
769
|
+
"""
|
|
770
|
+
resolve = get_resolve()
|
|
771
|
+
if resolve is None:
|
|
772
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
773
|
+
pm = resolve.GetProjectManager()
|
|
774
|
+
result = pm.ArchiveProject(project_name, archive_path, archive_src_media, archive_render_cache, archive_proxy_media)
|
|
775
|
+
return {"success": bool(result), "project_name": project_name, "archive_path": archive_path}
|
|
776
|
+
|
|
777
|
+
|
|
778
|
+
@mcp.tool()
|
|
779
|
+
def delete_project(project_name: str) -> Dict[str, Any]:
|
|
780
|
+
"""Delete a project from the current database. WARNING: This is irreversible.
|
|
781
|
+
|
|
782
|
+
Args:
|
|
783
|
+
project_name: Name of the project to delete.
|
|
784
|
+
"""
|
|
785
|
+
resolve = get_resolve()
|
|
786
|
+
if resolve is None:
|
|
787
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
788
|
+
pm = resolve.GetProjectManager()
|
|
789
|
+
result = pm.DeleteProject(project_name)
|
|
790
|
+
return {"success": bool(result), "project_name": project_name}
|
|
791
|
+
|
|
792
|
+
|
|
793
|
+
@mcp.tool()
|
|
794
|
+
def create_project_folder(folder_name: str) -> Dict[str, Any]:
|
|
795
|
+
"""Create a new folder in the current project folder location.
|
|
796
|
+
|
|
797
|
+
Args:
|
|
798
|
+
folder_name: Name of the folder to create.
|
|
799
|
+
"""
|
|
800
|
+
resolve = get_resolve()
|
|
801
|
+
if resolve is None:
|
|
802
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
803
|
+
pm = resolve.GetProjectManager()
|
|
804
|
+
result = pm.CreateFolder(folder_name)
|
|
805
|
+
return {"success": bool(result), "folder_name": folder_name}
|
|
806
|
+
|
|
807
|
+
|
|
808
|
+
@mcp.tool()
|
|
809
|
+
def delete_project_folder(folder_name: str) -> Dict[str, Any]:
|
|
810
|
+
"""Delete a folder from the current project folder location.
|
|
811
|
+
|
|
812
|
+
Args:
|
|
813
|
+
folder_name: Name of the folder to delete.
|
|
814
|
+
"""
|
|
815
|
+
resolve = get_resolve()
|
|
816
|
+
if resolve is None:
|
|
817
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
818
|
+
pm = resolve.GetProjectManager()
|
|
819
|
+
result = pm.DeleteFolder(folder_name)
|
|
820
|
+
return {"success": bool(result), "folder_name": folder_name}
|
|
821
|
+
|
|
822
|
+
|
|
823
|
+
@mcp.tool()
|
|
824
|
+
def get_project_folder_list() -> Dict[str, Any]:
|
|
825
|
+
"""Get list of folders in the current project folder location."""
|
|
826
|
+
resolve = get_resolve()
|
|
827
|
+
if resolve is None:
|
|
828
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
829
|
+
pm = resolve.GetProjectManager()
|
|
830
|
+
folders = pm.GetFolderListInCurrentFolder()
|
|
831
|
+
return {"folders": folders if folders else []}
|
|
832
|
+
|
|
833
|
+
|
|
834
|
+
@mcp.tool()
|
|
835
|
+
def goto_root_project_folder() -> Dict[str, Any]:
|
|
836
|
+
"""Navigate to the root project folder."""
|
|
837
|
+
resolve = get_resolve()
|
|
838
|
+
if resolve is None:
|
|
839
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
840
|
+
pm = resolve.GetProjectManager()
|
|
841
|
+
result = pm.GotoRootFolder()
|
|
842
|
+
return {"success": bool(result)}
|
|
843
|
+
|
|
844
|
+
|
|
845
|
+
@mcp.tool()
|
|
846
|
+
def goto_parent_project_folder() -> Dict[str, Any]:
|
|
847
|
+
"""Navigate up one level in the project folder hierarchy."""
|
|
848
|
+
resolve = get_resolve()
|
|
849
|
+
if resolve is None:
|
|
850
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
851
|
+
pm = resolve.GetProjectManager()
|
|
852
|
+
result = pm.GotoParentFolder()
|
|
853
|
+
return {"success": bool(result)}
|
|
854
|
+
|
|
855
|
+
|
|
856
|
+
@mcp.tool()
|
|
857
|
+
def get_current_project_folder() -> Dict[str, Any]:
|
|
858
|
+
"""Get the name of the current project folder."""
|
|
859
|
+
resolve = get_resolve()
|
|
860
|
+
if resolve is None:
|
|
861
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
862
|
+
pm = resolve.GetProjectManager()
|
|
863
|
+
folder = pm.GetCurrentFolder()
|
|
864
|
+
return {"current_folder": folder}
|
|
865
|
+
|
|
866
|
+
|
|
867
|
+
@mcp.tool()
|
|
868
|
+
def open_project_folder(folder_name: str) -> Dict[str, Any]:
|
|
869
|
+
"""Open/navigate into a project folder.
|
|
870
|
+
|
|
871
|
+
Args:
|
|
872
|
+
folder_name: Name of the folder to open.
|
|
873
|
+
"""
|
|
874
|
+
resolve = get_resolve()
|
|
875
|
+
if resolve is None:
|
|
876
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
877
|
+
pm = resolve.GetProjectManager()
|
|
878
|
+
result = pm.OpenFolder(folder_name)
|
|
879
|
+
return {"success": bool(result), "folder_name": folder_name}
|
|
880
|
+
|
|
881
|
+
|
|
882
|
+
@mcp.tool()
|
|
883
|
+
def import_project_from_file(file_path: str) -> Dict[str, Any]:
|
|
884
|
+
"""Import a project from a .drp file.
|
|
885
|
+
|
|
886
|
+
Args:
|
|
887
|
+
file_path: Absolute path to the .drp project file.
|
|
888
|
+
"""
|
|
889
|
+
resolve = get_resolve()
|
|
890
|
+
if resolve is None:
|
|
891
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
892
|
+
pm = resolve.GetProjectManager()
|
|
893
|
+
result = pm.ImportProject(file_path)
|
|
894
|
+
return {"success": bool(result), "file_path": file_path}
|
|
895
|
+
|
|
896
|
+
|
|
897
|
+
@mcp.tool()
|
|
898
|
+
def export_project_to_file(project_name: str, file_path: str, with_stills_and_luts: bool = True) -> Dict[str, Any]:
|
|
899
|
+
"""Export a project to a .drp file.
|
|
900
|
+
|
|
901
|
+
Args:
|
|
902
|
+
project_name: Name of the project to export.
|
|
903
|
+
file_path: Absolute path for the exported .drp file.
|
|
904
|
+
with_stills_and_luts: Include stills and LUTs in export. Default: True.
|
|
905
|
+
"""
|
|
906
|
+
resolve = get_resolve()
|
|
907
|
+
if resolve is None:
|
|
908
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
909
|
+
pm = resolve.GetProjectManager()
|
|
910
|
+
result = pm.ExportProject(project_name, file_path, with_stills_and_luts)
|
|
911
|
+
return {"success": bool(result), "project_name": project_name, "file_path": file_path}
|
|
912
|
+
|
|
913
|
+
|
|
914
|
+
@mcp.tool()
|
|
915
|
+
def restore_project(file_path: str) -> Dict[str, Any]:
|
|
916
|
+
"""Restore a project from an archive (.dra) file.
|
|
917
|
+
|
|
918
|
+
Args:
|
|
919
|
+
file_path: Absolute path to the .dra archive file.
|
|
920
|
+
"""
|
|
921
|
+
resolve = get_resolve()
|
|
922
|
+
if resolve is None:
|
|
923
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
924
|
+
pm = resolve.GetProjectManager()
|
|
925
|
+
result = pm.RestoreProject(file_path)
|
|
926
|
+
return {"success": bool(result), "file_path": file_path}
|
|
927
|
+
|
|
928
|
+
|
|
929
|
+
@mcp.tool()
|
|
930
|
+
def get_current_database() -> Dict[str, Any]:
|
|
931
|
+
"""Get information about the current database."""
|
|
932
|
+
resolve = get_resolve()
|
|
933
|
+
if resolve is None:
|
|
934
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
935
|
+
pm = resolve.GetProjectManager()
|
|
936
|
+
db = pm.GetCurrentDatabase()
|
|
937
|
+
return db if db else {"error": "Failed to get current database"}
|
|
938
|
+
|
|
939
|
+
|
|
940
|
+
@mcp.tool()
|
|
941
|
+
def get_database_list() -> Dict[str, Any]:
|
|
942
|
+
"""Get list of all available databases."""
|
|
943
|
+
resolve = get_resolve()
|
|
944
|
+
if resolve is None:
|
|
945
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
946
|
+
pm = resolve.GetProjectManager()
|
|
947
|
+
dbs = pm.GetDatabaseList()
|
|
948
|
+
return {"databases": dbs if dbs else []}
|
|
949
|
+
|
|
950
|
+
|
|
951
|
+
@mcp.tool()
|
|
952
|
+
def set_current_database(db_info: Dict[str, str]) -> Dict[str, Any]:
|
|
953
|
+
"""Switch to a different database.
|
|
954
|
+
|
|
955
|
+
Args:
|
|
956
|
+
db_info: Database info dict with keys 'DbType' and 'DbName'. Example: {"DbType": "Disk", "DbName": "Local Database"}
|
|
957
|
+
"""
|
|
958
|
+
resolve = get_resolve()
|
|
959
|
+
if resolve is None:
|
|
960
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
961
|
+
pm = resolve.GetProjectManager()
|
|
962
|
+
result = pm.SetCurrentDatabase(db_info)
|
|
963
|
+
return {"success": bool(result), "database": db_info}
|
|
964
|
+
|
|
965
|
+
|
|
966
|
+
@mcp.tool()
|
|
967
|
+
def set_project_name(name: str) -> Dict[str, Any]:
|
|
968
|
+
"""Rename the current project.
|
|
969
|
+
|
|
970
|
+
Args:
|
|
971
|
+
name: New name for the project.
|
|
972
|
+
"""
|
|
973
|
+
resolve = get_resolve()
|
|
974
|
+
if resolve is None:
|
|
975
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
976
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
977
|
+
if not project:
|
|
978
|
+
return {"error": "No project currently open"}
|
|
979
|
+
result = project.SetName(name)
|
|
980
|
+
return {"success": bool(result), "name": name}
|
|
981
|
+
|
|
982
|
+
|
|
983
|
+
@mcp.tool()
|
|
984
|
+
def get_timeline_by_index(index: int) -> Dict[str, Any]:
|
|
985
|
+
"""Get a timeline by its 1-based index.
|
|
986
|
+
|
|
987
|
+
Args:
|
|
988
|
+
index: 1-based timeline index.
|
|
989
|
+
"""
|
|
990
|
+
resolve = get_resolve()
|
|
991
|
+
if resolve is None:
|
|
992
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
993
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
994
|
+
if not project:
|
|
995
|
+
return {"error": "No project currently open"}
|
|
996
|
+
tl = project.GetTimelineByIndex(index)
|
|
997
|
+
if tl:
|
|
998
|
+
return {"name": tl.GetName(), "start_frame": tl.GetStartFrame(), "end_frame": tl.GetEndFrame(), "unique_id": tl.GetUniqueId()}
|
|
999
|
+
return {"error": f"No timeline at index {index}"}
|
|
1000
|
+
|
|
1001
|
+
|
|
1002
|
+
@mcp.tool()
|
|
1003
|
+
def get_project_preset_list() -> Dict[str, Any]:
|
|
1004
|
+
"""Get list of available project presets."""
|
|
1005
|
+
resolve = get_resolve()
|
|
1006
|
+
if resolve is None:
|
|
1007
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
1008
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
1009
|
+
if not project:
|
|
1010
|
+
return {"error": "No project currently open"}
|
|
1011
|
+
presets = project.GetPresetList()
|
|
1012
|
+
return {"presets": presets if presets else []}
|
|
1013
|
+
|
|
1014
|
+
|
|
1015
|
+
@mcp.tool()
|
|
1016
|
+
def set_project_preset(preset_name: str) -> Dict[str, Any]:
|
|
1017
|
+
"""Apply a project preset to the current project.
|
|
1018
|
+
|
|
1019
|
+
Args:
|
|
1020
|
+
preset_name: Name of the preset to apply.
|
|
1021
|
+
"""
|
|
1022
|
+
resolve = get_resolve()
|
|
1023
|
+
if resolve is None:
|
|
1024
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
1025
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
1026
|
+
if not project:
|
|
1027
|
+
return {"error": "No project currently open"}
|
|
1028
|
+
result = project.SetPreset(preset_name)
|
|
1029
|
+
return {"success": bool(result), "preset_name": preset_name}
|
|
1030
|
+
|
|
1031
|
+
|
|
1032
|
+
@mcp.tool()
|
|
1033
|
+
def delete_render_job(job_id: str) -> Dict[str, Any]:
|
|
1034
|
+
"""Delete a specific render job by its ID.
|
|
1035
|
+
|
|
1036
|
+
Args:
|
|
1037
|
+
job_id: The unique ID of the render job to delete.
|
|
1038
|
+
"""
|
|
1039
|
+
resolve = get_resolve()
|
|
1040
|
+
if resolve is None:
|
|
1041
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
1042
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
1043
|
+
if not project:
|
|
1044
|
+
return {"error": "No project currently open"}
|
|
1045
|
+
result = project.DeleteRenderJob(job_id)
|
|
1046
|
+
return {"success": bool(result), "job_id": job_id}
|
|
1047
|
+
|
|
1048
|
+
|
|
1049
|
+
@mcp.tool()
|
|
1050
|
+
def get_render_job_list() -> Dict[str, Any]:
|
|
1051
|
+
"""Get list of all render jobs in the queue."""
|
|
1052
|
+
resolve = get_resolve()
|
|
1053
|
+
if resolve is None:
|
|
1054
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
1055
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
1056
|
+
if not project:
|
|
1057
|
+
return {"error": "No project currently open"}
|
|
1058
|
+
jobs = project.GetRenderJobList()
|
|
1059
|
+
return {"render_jobs": jobs if jobs else []}
|
|
1060
|
+
|
|
1061
|
+
|
|
1062
|
+
@mcp.tool()
|
|
1063
|
+
def start_rendering_jobs(job_ids: Optional[List[str]] = None, is_interactive_mode: bool = False) -> Dict[str, Any]:
|
|
1064
|
+
"""Start rendering jobs. If no job IDs specified, renders all queued jobs.
|
|
1065
|
+
|
|
1066
|
+
Args:
|
|
1067
|
+
job_ids: Optional list of job IDs to render. If None, renders all.
|
|
1068
|
+
is_interactive_mode: If True, enables interactive rendering mode.
|
|
1069
|
+
"""
|
|
1070
|
+
resolve = get_resolve()
|
|
1071
|
+
if resolve is None:
|
|
1072
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
1073
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
1074
|
+
if not project:
|
|
1075
|
+
return {"error": "No project currently open"}
|
|
1076
|
+
if job_ids:
|
|
1077
|
+
result = project.StartRendering(job_ids, is_interactive_mode)
|
|
1078
|
+
else:
|
|
1079
|
+
result = project.StartRendering(is_interactive_mode)
|
|
1080
|
+
return {"success": bool(result)}
|
|
1081
|
+
|
|
1082
|
+
|
|
1083
|
+
@mcp.tool()
|
|
1084
|
+
def stop_rendering() -> Dict[str, Any]:
|
|
1085
|
+
"""Stop the current rendering process."""
|
|
1086
|
+
resolve = get_resolve()
|
|
1087
|
+
if resolve is None:
|
|
1088
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
1089
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
1090
|
+
if not project:
|
|
1091
|
+
return {"error": "No project currently open"}
|
|
1092
|
+
project.StopRendering()
|
|
1093
|
+
return {"success": True}
|
|
1094
|
+
|
|
1095
|
+
|
|
1096
|
+
@mcp.tool()
|
|
1097
|
+
def is_rendering_in_progress() -> Dict[str, Any]:
|
|
1098
|
+
"""Check if rendering is currently in progress."""
|
|
1099
|
+
resolve = get_resolve()
|
|
1100
|
+
if resolve is None:
|
|
1101
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
1102
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
1103
|
+
if not project:
|
|
1104
|
+
return {"error": "No project currently open"}
|
|
1105
|
+
result = project.IsRenderingInProgress()
|
|
1106
|
+
return {"is_rendering": bool(result)}
|
|
1107
|
+
|
|
1108
|
+
|
|
1109
|
+
@mcp.tool()
|
|
1110
|
+
def load_render_preset(preset_name: str) -> Dict[str, Any]:
|
|
1111
|
+
"""Load a render preset by name.
|
|
1112
|
+
|
|
1113
|
+
Args:
|
|
1114
|
+
preset_name: Name of the render preset to load.
|
|
1115
|
+
"""
|
|
1116
|
+
resolve = get_resolve()
|
|
1117
|
+
if resolve is None:
|
|
1118
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
1119
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
1120
|
+
if not project:
|
|
1121
|
+
return {"error": "No project currently open"}
|
|
1122
|
+
result = project.LoadRenderPreset(preset_name)
|
|
1123
|
+
return {"success": bool(result), "preset_name": preset_name}
|
|
1124
|
+
|
|
1125
|
+
|
|
1126
|
+
@mcp.tool()
|
|
1127
|
+
def save_as_new_render_preset(preset_name: str) -> Dict[str, Any]:
|
|
1128
|
+
"""Save current render settings as a new preset.
|
|
1129
|
+
|
|
1130
|
+
Args:
|
|
1131
|
+
preset_name: Name for the new render preset.
|
|
1132
|
+
"""
|
|
1133
|
+
resolve = get_resolve()
|
|
1134
|
+
if resolve is None:
|
|
1135
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
1136
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
1137
|
+
if not project:
|
|
1138
|
+
return {"error": "No project currently open"}
|
|
1139
|
+
result = project.SaveAsNewRenderPreset(preset_name)
|
|
1140
|
+
return {"success": bool(result), "preset_name": preset_name}
|
|
1141
|
+
|
|
1142
|
+
|
|
1143
|
+
@mcp.tool()
|
|
1144
|
+
def delete_render_preset(preset_name: str) -> Dict[str, Any]:
|
|
1145
|
+
"""Delete a render preset.
|
|
1146
|
+
|
|
1147
|
+
Args:
|
|
1148
|
+
preset_name: Name of the render preset to delete.
|
|
1149
|
+
"""
|
|
1150
|
+
resolve = get_resolve()
|
|
1151
|
+
if resolve is None:
|
|
1152
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
1153
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
1154
|
+
if not project:
|
|
1155
|
+
return {"error": "No project currently open"}
|
|
1156
|
+
result = project.DeleteRenderPreset(preset_name)
|
|
1157
|
+
return {"success": bool(result), "preset_name": preset_name}
|
|
1158
|
+
|
|
1159
|
+
|
|
1160
|
+
@mcp.tool()
|
|
1161
|
+
def set_render_settings(settings: Dict[str, Any]) -> Dict[str, Any]:
|
|
1162
|
+
"""Set render settings for the current project.
|
|
1163
|
+
|
|
1164
|
+
Mirrors Project.SetRenderSettings({settings}) per docs lines 765-799.
|
|
1165
|
+
|
|
1166
|
+
Args:
|
|
1167
|
+
settings: Dict of render settings. All documented keys are forwarded:
|
|
1168
|
+
SelectAllFrames (bool), MarkIn (int), MarkOut (int),
|
|
1169
|
+
TargetDir (str), CustomName (str), UniqueFilenameStyle (0=Prefix/1=Suffix),
|
|
1170
|
+
ExportVideo (bool), ExportAudio (bool), FormatWidth (int),
|
|
1171
|
+
FormatHeight (int), FrameRate (float), PixelAspectRatio (float),
|
|
1172
|
+
VideoQuality (int/str), AudioCodec (str), AudioBitDepth (int),
|
|
1173
|
+
AudioSampleRate (int), ColorSpaceTag (str), GammaTag (str),
|
|
1174
|
+
ExportAlpha (bool), EncodingProfile (str), MultiPassEncode (bool),
|
|
1175
|
+
AlphaMode (int), NetworkOptimization (bool), ClipStartFrame (int),
|
|
1176
|
+
TimelineStartTimecode (str), ReplaceExistingFilesInPlace (bool),
|
|
1177
|
+
ExportSubtitle (bool), SubtitleFormat ("BurnIn", "EmbeddedCaptions", "SeparateFile").
|
|
1178
|
+
"""
|
|
1179
|
+
resolve = get_resolve()
|
|
1180
|
+
if resolve is None:
|
|
1181
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
1182
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
1183
|
+
if not project:
|
|
1184
|
+
return {"error": "No project currently open"}
|
|
1185
|
+
result = project.SetRenderSettings(settings)
|
|
1186
|
+
return {"success": bool(result)}
|
|
1187
|
+
|
|
1188
|
+
|
|
1189
|
+
@mcp.tool()
|
|
1190
|
+
def get_render_job_status(job_id: str) -> Dict[str, Any]:
|
|
1191
|
+
"""Get the status of a specific render job.
|
|
1192
|
+
|
|
1193
|
+
Args:
|
|
1194
|
+
job_id: The unique ID of the render job.
|
|
1195
|
+
"""
|
|
1196
|
+
resolve = get_resolve()
|
|
1197
|
+
if resolve is None:
|
|
1198
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
1199
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
1200
|
+
if not project:
|
|
1201
|
+
return {"error": "No project currently open"}
|
|
1202
|
+
status = project.GetRenderJobStatus(job_id)
|
|
1203
|
+
return status if status else {"error": f"No render job with ID {job_id}"}
|
|
1204
|
+
|
|
1205
|
+
|
|
1206
|
+
@mcp.tool()
|
|
1207
|
+
def get_render_formats() -> Dict[str, Any]:
|
|
1208
|
+
"""Get all available render formats."""
|
|
1209
|
+
resolve = get_resolve()
|
|
1210
|
+
if resolve is None:
|
|
1211
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
1212
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
1213
|
+
if not project:
|
|
1214
|
+
return {"error": "No project currently open"}
|
|
1215
|
+
formats = project.GetRenderFormats()
|
|
1216
|
+
return {"formats": formats if formats else {}}
|
|
1217
|
+
|
|
1218
|
+
|
|
1219
|
+
@mcp.tool()
|
|
1220
|
+
def get_render_codecs(format_name: str) -> Dict[str, Any]:
|
|
1221
|
+
"""Get available codecs for a given render format.
|
|
1222
|
+
|
|
1223
|
+
Args:
|
|
1224
|
+
format_name: Render format name (e.g. 'mp4', 'mov', 'avi').
|
|
1225
|
+
"""
|
|
1226
|
+
resolve = get_resolve()
|
|
1227
|
+
if resolve is None:
|
|
1228
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
1229
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
1230
|
+
if not project:
|
|
1231
|
+
return {"error": "No project currently open"}
|
|
1232
|
+
codecs = project.GetRenderCodecs(format_name)
|
|
1233
|
+
return {"format": format_name, "codecs": codecs if codecs else {}}
|
|
1234
|
+
|
|
1235
|
+
|
|
1236
|
+
@mcp.tool()
|
|
1237
|
+
def get_current_render_format_and_codec() -> Dict[str, Any]:
|
|
1238
|
+
"""Get the current render format and codec setting."""
|
|
1239
|
+
resolve = get_resolve()
|
|
1240
|
+
if resolve is None:
|
|
1241
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
1242
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
1243
|
+
if not project:
|
|
1244
|
+
return {"error": "No project currently open"}
|
|
1245
|
+
result = project.GetCurrentRenderFormatAndCodec()
|
|
1246
|
+
return result if result else {"error": "Failed to get render format and codec"}
|
|
1247
|
+
|
|
1248
|
+
|
|
1249
|
+
@mcp.tool()
|
|
1250
|
+
def set_current_render_format_and_codec(format_name: str, codec_name: str) -> Dict[str, Any]:
|
|
1251
|
+
"""Set the render format and codec.
|
|
1252
|
+
|
|
1253
|
+
Args:
|
|
1254
|
+
format_name: Render format (e.g. 'mp4', 'mov').
|
|
1255
|
+
codec_name: Codec name (e.g. 'H264', 'H265', 'ProRes422HQ').
|
|
1256
|
+
"""
|
|
1257
|
+
resolve = get_resolve()
|
|
1258
|
+
if resolve is None:
|
|
1259
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
1260
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
1261
|
+
if not project:
|
|
1262
|
+
return {"error": "No project currently open"}
|
|
1263
|
+
result = project.SetCurrentRenderFormatAndCodec(format_name, codec_name)
|
|
1264
|
+
return {"success": bool(result), "format": format_name, "codec": codec_name}
|
|
1265
|
+
|
|
1266
|
+
|
|
1267
|
+
@mcp.tool()
|
|
1268
|
+
def get_current_render_mode() -> Dict[str, Any]:
|
|
1269
|
+
"""Get the current render mode (0=Individual Clips, 1=Single Clip)."""
|
|
1270
|
+
resolve = get_resolve()
|
|
1271
|
+
if resolve is None:
|
|
1272
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
1273
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
1274
|
+
if not project:
|
|
1275
|
+
return {"error": "No project currently open"}
|
|
1276
|
+
mode = project.GetCurrentRenderMode()
|
|
1277
|
+
return {"render_mode": mode, "mode_name": "Individual Clips" if mode == 0 else "Single Clip"}
|
|
1278
|
+
|
|
1279
|
+
|
|
1280
|
+
@mcp.tool()
|
|
1281
|
+
def set_current_render_mode(mode: int) -> Dict[str, Any]:
|
|
1282
|
+
"""Set the render mode.
|
|
1283
|
+
|
|
1284
|
+
Args:
|
|
1285
|
+
mode: 0 for Individual Clips, 1 for Single Clip.
|
|
1286
|
+
"""
|
|
1287
|
+
resolve = get_resolve()
|
|
1288
|
+
if resolve is None:
|
|
1289
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
1290
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
1291
|
+
if not project:
|
|
1292
|
+
return {"error": "No project currently open"}
|
|
1293
|
+
result = project.SetCurrentRenderMode(mode)
|
|
1294
|
+
return {"success": bool(result), "render_mode": mode}
|
|
1295
|
+
|
|
1296
|
+
|
|
1297
|
+
@mcp.tool()
|
|
1298
|
+
def get_render_resolutions(format_name: str, codec_name: str) -> Dict[str, Any]:
|
|
1299
|
+
"""Get available render resolutions for a format/codec combination.
|
|
1300
|
+
|
|
1301
|
+
Args:
|
|
1302
|
+
format_name: Render format (e.g. 'mp4').
|
|
1303
|
+
codec_name: Codec name (e.g. 'H264').
|
|
1304
|
+
"""
|
|
1305
|
+
resolve = get_resolve()
|
|
1306
|
+
if resolve is None:
|
|
1307
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
1308
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
1309
|
+
if not project:
|
|
1310
|
+
return {"error": "No project currently open"}
|
|
1311
|
+
resolutions = project.GetRenderResolutions(format_name, codec_name)
|
|
1312
|
+
return {"format": format_name, "codec": codec_name, "resolutions": resolutions if resolutions else []}
|
|
1313
|
+
|
|
1314
|
+
|
|
1315
|
+
@mcp.tool()
|
|
1316
|
+
def refresh_lut_list() -> Dict[str, Any]:
|
|
1317
|
+
"""Refresh the LUT list in the project. Call after adding new LUT files."""
|
|
1318
|
+
resolve = get_resolve()
|
|
1319
|
+
if resolve is None:
|
|
1320
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
1321
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
1322
|
+
if not project:
|
|
1323
|
+
return {"error": "No project currently open"}
|
|
1324
|
+
result = project.RefreshLUTList()
|
|
1325
|
+
return {"success": bool(result)}
|
|
1326
|
+
|
|
1327
|
+
|
|
1328
|
+
@mcp.tool()
|
|
1329
|
+
def get_project_unique_id() -> Dict[str, Any]:
|
|
1330
|
+
"""Get the unique ID of the current project."""
|
|
1331
|
+
resolve = get_resolve()
|
|
1332
|
+
if resolve is None:
|
|
1333
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
1334
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
1335
|
+
if not project:
|
|
1336
|
+
return {"error": "No project currently open"}
|
|
1337
|
+
uid = project.GetUniqueId()
|
|
1338
|
+
return {"unique_id": uid}
|
|
1339
|
+
|
|
1340
|
+
|
|
1341
|
+
@mcp.tool()
|
|
1342
|
+
def insert_audio_to_current_track(file_path: str) -> Dict[str, Any]:
|
|
1343
|
+
"""Insert audio file to current track at playhead position.
|
|
1344
|
+
|
|
1345
|
+
Args:
|
|
1346
|
+
file_path: Absolute path to the audio file.
|
|
1347
|
+
"""
|
|
1348
|
+
resolve = get_resolve()
|
|
1349
|
+
if resolve is None:
|
|
1350
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
1351
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
1352
|
+
if not project:
|
|
1353
|
+
return {"error": "No project currently open"}
|
|
1354
|
+
result = project.InsertAudioToCurrentTrackAtPlayhead(file_path)
|
|
1355
|
+
return {"success": bool(result), "file_path": file_path}
|
|
1356
|
+
|
|
1357
|
+
|
|
1358
|
+
@mcp.tool()
|
|
1359
|
+
def load_burn_in_preset(preset_name: str) -> Dict[str, Any]:
|
|
1360
|
+
"""Load a burn-in preset by name for the project.
|
|
1361
|
+
|
|
1362
|
+
Args:
|
|
1363
|
+
preset_name: Name of the burn-in preset to load.
|
|
1364
|
+
"""
|
|
1365
|
+
resolve = get_resolve()
|
|
1366
|
+
if resolve is None:
|
|
1367
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
1368
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
1369
|
+
if not project:
|
|
1370
|
+
return {"error": "No project currently open"}
|
|
1371
|
+
result = project.LoadBurnInPreset(preset_name)
|
|
1372
|
+
return {"success": bool(result), "preset_name": preset_name}
|
|
1373
|
+
|
|
1374
|
+
|
|
1375
|
+
@mcp.tool()
|
|
1376
|
+
def export_current_frame_as_still(file_path: str) -> Dict[str, Any]:
|
|
1377
|
+
"""Export the current frame as a still image.
|
|
1378
|
+
|
|
1379
|
+
Args:
|
|
1380
|
+
file_path: Absolute path for the exported still image.
|
|
1381
|
+
"""
|
|
1382
|
+
resolve = get_resolve()
|
|
1383
|
+
if resolve is None:
|
|
1384
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
1385
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
1386
|
+
if not project:
|
|
1387
|
+
return {"error": "No project currently open"}
|
|
1388
|
+
result = project.ExportCurrentFrameAsStill(file_path)
|
|
1389
|
+
return {"success": bool(result), "file_path": file_path}
|
|
1390
|
+
|
|
1391
|
+
|
|
1392
|
+
@mcp.tool()
|
|
1393
|
+
def get_color_groups_list() -> Dict[str, Any]:
|
|
1394
|
+
"""Get list of all color groups in the current project."""
|
|
1395
|
+
resolve = get_resolve()
|
|
1396
|
+
if resolve is None:
|
|
1397
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
1398
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
1399
|
+
if not project:
|
|
1400
|
+
return {"error": "No project currently open"}
|
|
1401
|
+
groups = project.GetColorGroupsList()
|
|
1402
|
+
if groups:
|
|
1403
|
+
return {"color_groups": [{"name": g.GetName()} for g in groups]}
|
|
1404
|
+
return {"color_groups": []}
|
|
1405
|
+
|
|
1406
|
+
|
|
1407
|
+
@mcp.tool()
|
|
1408
|
+
def add_color_group(group_name: str) -> Dict[str, Any]:
|
|
1409
|
+
"""Create a new color group in the current project.
|
|
1410
|
+
|
|
1411
|
+
Args:
|
|
1412
|
+
group_name: Name for the new color group.
|
|
1413
|
+
"""
|
|
1414
|
+
resolve = get_resolve()
|
|
1415
|
+
if resolve is None:
|
|
1416
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
1417
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
1418
|
+
if not project:
|
|
1419
|
+
return {"error": "No project currently open"}
|
|
1420
|
+
result = project.AddColorGroup(group_name)
|
|
1421
|
+
return {"success": bool(result), "group_name": group_name}
|
|
1422
|
+
|
|
1423
|
+
|
|
1424
|
+
@mcp.tool()
|
|
1425
|
+
def delete_color_group(group_name: str) -> Dict[str, Any]:
|
|
1426
|
+
"""Delete a color group from the current project.
|
|
1427
|
+
|
|
1428
|
+
Args:
|
|
1429
|
+
group_name: Name of the color group to delete.
|
|
1430
|
+
"""
|
|
1431
|
+
resolve = get_resolve()
|
|
1432
|
+
if resolve is None:
|
|
1433
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
1434
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
1435
|
+
if not project:
|
|
1436
|
+
return {"error": "No project currently open"}
|
|
1437
|
+
# Find the group by name
|
|
1438
|
+
groups = project.GetColorGroupsList()
|
|
1439
|
+
target = None
|
|
1440
|
+
if groups:
|
|
1441
|
+
for g in groups:
|
|
1442
|
+
if g.GetName() == group_name:
|
|
1443
|
+
target = g
|
|
1444
|
+
break
|
|
1445
|
+
if not target:
|
|
1446
|
+
return {"error": f"Color group '{group_name}' not found"}
|
|
1447
|
+
result = project.DeleteColorGroup(target)
|
|
1448
|
+
return {"success": bool(result), "group_name": group_name}
|
|
1449
|
+
|
|
1450
|
+
|
|
1451
|
+
@mcp.tool()
|
|
1452
|
+
def rename_color_group(group_name: str, new_name: str) -> Dict[str, Any]:
|
|
1453
|
+
"""Rename a color group.
|
|
1454
|
+
|
|
1455
|
+
Mirrors ColorGroup.SetName(groupName).
|
|
1456
|
+
|
|
1457
|
+
Args:
|
|
1458
|
+
group_name: Current name of the color group.
|
|
1459
|
+
new_name: New name to assign.
|
|
1460
|
+
"""
|
|
1461
|
+
resolve = get_resolve()
|
|
1462
|
+
if resolve is None:
|
|
1463
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
1464
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
1465
|
+
if not project:
|
|
1466
|
+
return {"error": "No project currently open"}
|
|
1467
|
+
groups = project.GetColorGroupsList() or []
|
|
1468
|
+
target = next((g for g in groups if g.GetName() == group_name), None)
|
|
1469
|
+
if not target:
|
|
1470
|
+
return {"error": f"Color group '{group_name}' not found"}
|
|
1471
|
+
return {"success": bool(target.SetName(new_name)), "old_name": group_name, "new_name": new_name}
|
|
1472
|
+
|
|
1473
|
+
|
|
1474
|
+
@mcp.tool()
|
|
1475
|
+
def apply_fairlight_preset_to_current_timeline(preset_name: str) -> Dict[str, Any]:
|
|
1476
|
+
"""Apply a Fairlight preset to the current timeline.
|
|
1477
|
+
|
|
1478
|
+
Args:
|
|
1479
|
+
preset_name: Name of the Fairlight preset to apply.
|
|
1480
|
+
"""
|
|
1481
|
+
resolve = get_resolve()
|
|
1482
|
+
if resolve is None:
|
|
1483
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
1484
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
1485
|
+
if not project:
|
|
1486
|
+
return {"error": "No project currently open"}
|
|
1487
|
+
missing = _requires_method(project, "ApplyFairlightPresetToCurrentTimeline", "20.2.2")
|
|
1488
|
+
if missing:
|
|
1489
|
+
return missing
|
|
1490
|
+
result = project.ApplyFairlightPresetToCurrentTimeline(preset_name)
|
|
1491
|
+
return {"success": bool(result), "preset_name": preset_name}
|
|
1492
|
+
|
|
1493
|
+
|
|
1494
|
+
@mcp.tool()
|
|
1495
|
+
def get_quick_export_render_presets() -> Dict[str, Any]:
|
|
1496
|
+
"""Get list of available quick export render presets."""
|
|
1497
|
+
resolve = get_resolve()
|
|
1498
|
+
if resolve is None:
|
|
1499
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
1500
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
1501
|
+
if not project:
|
|
1502
|
+
return {"error": "No project currently open"}
|
|
1503
|
+
presets = project.GetQuickExportRenderPresets()
|
|
1504
|
+
return {"presets": presets if presets else []}
|
|
1505
|
+
|
|
1506
|
+
|
|
1507
|
+
@mcp.tool()
|
|
1508
|
+
def render_with_quick_export(
|
|
1509
|
+
preset_name: str,
|
|
1510
|
+
target_dir: Optional[str] = None,
|
|
1511
|
+
custom_name: Optional[str] = None,
|
|
1512
|
+
video_quality: Optional[Any] = None,
|
|
1513
|
+
enable_upload: Optional[bool] = None,
|
|
1514
|
+
) -> Dict[str, Any]:
|
|
1515
|
+
"""Render the current timeline using a Quick Export preset.
|
|
1516
|
+
|
|
1517
|
+
Mirrors Project.RenderWithQuickExport(preset_name, {param_dict}) per docs line 179.
|
|
1518
|
+
|
|
1519
|
+
Args:
|
|
1520
|
+
preset_name: Name of the Quick Export preset (from get_quick_export_render_presets).
|
|
1521
|
+
target_dir: Output directory; maps to TargetDir in param_dict.
|
|
1522
|
+
custom_name: Output filename; maps to CustomName.
|
|
1523
|
+
video_quality: Video quality setting (int or string per render-settings spec); maps to VideoQuality.
|
|
1524
|
+
enable_upload: Enable direct upload for supported web presets; maps to EnableUpload.
|
|
1525
|
+
"""
|
|
1526
|
+
resolve = get_resolve()
|
|
1527
|
+
if resolve is None:
|
|
1528
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
1529
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
1530
|
+
if not project:
|
|
1531
|
+
return {"error": "No project currently open"}
|
|
1532
|
+
param_dict: Dict[str, Any] = {}
|
|
1533
|
+
if target_dir is not None:
|
|
1534
|
+
param_dict["TargetDir"] = target_dir
|
|
1535
|
+
if custom_name is not None:
|
|
1536
|
+
param_dict["CustomName"] = custom_name
|
|
1537
|
+
if video_quality is not None:
|
|
1538
|
+
param_dict["VideoQuality"] = video_quality
|
|
1539
|
+
if enable_upload is not None:
|
|
1540
|
+
param_dict["EnableUpload"] = bool(enable_upload)
|
|
1541
|
+
if param_dict:
|
|
1542
|
+
result = project.RenderWithQuickExport(preset_name, param_dict)
|
|
1543
|
+
else:
|
|
1544
|
+
result = project.RenderWithQuickExport(preset_name)
|
|
1545
|
+
if not result:
|
|
1546
|
+
return {"success": False, "preset_name": preset_name, "error": "RenderWithQuickExport returned no status"}
|
|
1547
|
+
if isinstance(result, str):
|
|
1548
|
+
return {"success": False, "preset_name": preset_name, "error": result}
|
|
1549
|
+
return {"success": True, "preset_name": preset_name, "status": result}
|
|
1550
|
+
|
|
1551
|
+
|
|
1552
|
+
@mcp.tool()
|
|
1553
|
+
def add_render_job() -> Dict[str, Any]:
|
|
1554
|
+
"""Add a render job based on current render settings to the render queue.
|
|
1555
|
+
|
|
1556
|
+
Returns the unique job ID string for the new render job.
|
|
1557
|
+
Configure render settings first with set_render_settings, set_render_format_and_codec, etc.
|
|
1558
|
+
"""
|
|
1559
|
+
resolve = get_resolve()
|
|
1560
|
+
if resolve is None:
|
|
1561
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
1562
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
1563
|
+
if not project:
|
|
1564
|
+
return {"error": "No project currently open"}
|
|
1565
|
+
job_id = project.AddRenderJob()
|
|
1566
|
+
if job_id:
|
|
1567
|
+
return {"success": True, "job_id": job_id}
|
|
1568
|
+
return {"success": False, "error": "Failed to add render job. Check render settings are configured."}
|
|
1569
|
+
|
|
1570
|
+
|
|
1571
|
+
@mcp.tool()
|
|
1572
|
+
def load_cloud_project(project_name: str, project_media_path: str, sync_mode: str = "proxy") -> Dict[str, Any]:
|
|
1573
|
+
"""Load a cloud project from DaVinci Resolve cloud.
|
|
1574
|
+
|
|
1575
|
+
Args:
|
|
1576
|
+
project_name: Name of the cloud project to load.
|
|
1577
|
+
project_media_path: Local path for project media cache.
|
|
1578
|
+
sync_mode: Sync mode - 'proxy' or 'full' (default: 'proxy').
|
|
1579
|
+
"""
|
|
1580
|
+
resolve = get_resolve()
|
|
1581
|
+
if resolve is None:
|
|
1582
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
1583
|
+
pm = resolve.GetProjectManager()
|
|
1584
|
+
if not pm:
|
|
1585
|
+
return {"error": "Failed to get ProjectManager"}
|
|
1586
|
+
cloud_settings = {
|
|
1587
|
+
resolve.CLOUD_SETTING_PROJECT_NAME: project_name,
|
|
1588
|
+
resolve.CLOUD_SETTING_PROJECT_MEDIA_PATH: project_media_path,
|
|
1589
|
+
resolve.CLOUD_SETTING_SYNC_MODE: sync_mode,
|
|
1590
|
+
}
|
|
1591
|
+
project = pm.LoadCloudProject(cloud_settings)
|
|
1592
|
+
if project:
|
|
1593
|
+
return {"success": True, "project_name": project.GetName()}
|
|
1594
|
+
return {"success": False, "error": "Failed to load cloud project. Check cloud settings and connectivity."}
|