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,287 @@
|
|
|
1
|
+
"""Folder-oriented tools for media pool folders."""
|
|
2
|
+
|
|
3
|
+
from src.granular.common import * # noqa: F401,F403
|
|
4
|
+
|
|
5
|
+
resolve = ResolveProxy()
|
|
6
|
+
|
|
7
|
+
@mcp.tool()
|
|
8
|
+
def export_folder(folder_name: str, export_path: str, export_type: str = "DRB") -> str:
|
|
9
|
+
"""Export a folder to a DRB file or other format.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
folder_name: Name of the folder to export
|
|
13
|
+
export_path: Path to save the exported file
|
|
14
|
+
export_type: Export format (DRB is default and currently the only supported option)
|
|
15
|
+
"""
|
|
16
|
+
pm, current_project = get_current_project()
|
|
17
|
+
if not current_project:
|
|
18
|
+
return "Error: No project currently open"
|
|
19
|
+
|
|
20
|
+
media_pool = current_project.GetMediaPool()
|
|
21
|
+
if not media_pool:
|
|
22
|
+
return "Error: Failed to get Media Pool"
|
|
23
|
+
|
|
24
|
+
# Find the folder by name
|
|
25
|
+
target_folder = None
|
|
26
|
+
root_folder = media_pool.GetRootFolder()
|
|
27
|
+
|
|
28
|
+
if folder_name.lower() == "root" or folder_name.lower() == "master":
|
|
29
|
+
target_folder = root_folder
|
|
30
|
+
else:
|
|
31
|
+
# Search for the folder by name
|
|
32
|
+
folders = get_all_media_pool_folders(media_pool)
|
|
33
|
+
for folder in folders:
|
|
34
|
+
if folder.GetName() == folder_name:
|
|
35
|
+
target_folder = folder
|
|
36
|
+
break
|
|
37
|
+
|
|
38
|
+
if not target_folder:
|
|
39
|
+
return f"Error: Folder '{folder_name}' not found in Media Pool"
|
|
40
|
+
|
|
41
|
+
# Check if directory exists, create if not
|
|
42
|
+
export_dir = os.path.dirname(export_path)
|
|
43
|
+
if not os.path.exists(export_dir) and export_dir:
|
|
44
|
+
try:
|
|
45
|
+
os.makedirs(export_dir)
|
|
46
|
+
except Exception as e:
|
|
47
|
+
return f"Error creating directory for export: {str(e)}"
|
|
48
|
+
|
|
49
|
+
# Export the folder
|
|
50
|
+
try:
|
|
51
|
+
result = target_folder.Export(export_path)
|
|
52
|
+
if result:
|
|
53
|
+
return f"Successfully exported folder '{folder_name}' to '{export_path}'"
|
|
54
|
+
else:
|
|
55
|
+
return f"Failed to export folder '{folder_name}'"
|
|
56
|
+
except Exception as e:
|
|
57
|
+
return f"Error exporting folder: {str(e)}"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@mcp.tool()
|
|
61
|
+
def transcribe_folder_audio(folder_name: str, language: str = "en-US") -> str:
|
|
62
|
+
"""Transcribe audio for all clips in a folder.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
folder_name: Name of the folder containing clips to transcribe
|
|
66
|
+
language: Language code for transcription (default: en-US)
|
|
67
|
+
"""
|
|
68
|
+
pm, current_project = get_current_project()
|
|
69
|
+
if not current_project:
|
|
70
|
+
return "Error: No project currently open"
|
|
71
|
+
|
|
72
|
+
media_pool = current_project.GetMediaPool()
|
|
73
|
+
if not media_pool:
|
|
74
|
+
return "Error: Failed to get Media Pool"
|
|
75
|
+
|
|
76
|
+
# Find the folder by name
|
|
77
|
+
target_folder = None
|
|
78
|
+
root_folder = media_pool.GetRootFolder()
|
|
79
|
+
|
|
80
|
+
if folder_name.lower() == "root" or folder_name.lower() == "master":
|
|
81
|
+
target_folder = root_folder
|
|
82
|
+
else:
|
|
83
|
+
# Search for the folder by name
|
|
84
|
+
folders = get_all_media_pool_folders(media_pool)
|
|
85
|
+
for folder in folders:
|
|
86
|
+
if folder.GetName() == folder_name:
|
|
87
|
+
target_folder = folder
|
|
88
|
+
break
|
|
89
|
+
|
|
90
|
+
if not target_folder:
|
|
91
|
+
return f"Error: Folder '{folder_name}' not found in Media Pool"
|
|
92
|
+
|
|
93
|
+
# Transcribe audio in the folder
|
|
94
|
+
try:
|
|
95
|
+
result = target_folder.TranscribeAudio(language)
|
|
96
|
+
if result:
|
|
97
|
+
return f"Successfully started audio transcription for folder '{folder_name}' in language '{language}'"
|
|
98
|
+
else:
|
|
99
|
+
return f"Failed to start audio transcription for folder '{folder_name}'"
|
|
100
|
+
except Exception as e:
|
|
101
|
+
return f"Error during audio transcription: {str(e)}"
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@mcp.tool()
|
|
105
|
+
def clear_folder_transcription(folder_name: str) -> str:
|
|
106
|
+
"""Clear audio transcription for all clips in a folder.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
folder_name: Name of the folder to clear transcriptions from
|
|
110
|
+
"""
|
|
111
|
+
pm, current_project = get_current_project()
|
|
112
|
+
if not current_project:
|
|
113
|
+
return "Error: No project currently open"
|
|
114
|
+
|
|
115
|
+
media_pool = current_project.GetMediaPool()
|
|
116
|
+
if not media_pool:
|
|
117
|
+
return "Error: Failed to get Media Pool"
|
|
118
|
+
|
|
119
|
+
# Find the folder by name
|
|
120
|
+
target_folder = None
|
|
121
|
+
root_folder = media_pool.GetRootFolder()
|
|
122
|
+
|
|
123
|
+
if folder_name.lower() == "root" or folder_name.lower() == "master":
|
|
124
|
+
target_folder = root_folder
|
|
125
|
+
else:
|
|
126
|
+
# Search for the folder by name
|
|
127
|
+
folders = get_all_media_pool_folders(media_pool)
|
|
128
|
+
for folder in folders:
|
|
129
|
+
if folder.GetName() == folder_name:
|
|
130
|
+
target_folder = folder
|
|
131
|
+
break
|
|
132
|
+
|
|
133
|
+
if not target_folder:
|
|
134
|
+
return f"Error: Folder '{folder_name}' not found in Media Pool"
|
|
135
|
+
|
|
136
|
+
# Clear transcription for the folder
|
|
137
|
+
try:
|
|
138
|
+
result = target_folder.ClearTranscription()
|
|
139
|
+
if result:
|
|
140
|
+
return f"Successfully cleared audio transcription for folder '{folder_name}'"
|
|
141
|
+
else:
|
|
142
|
+
return f"Failed to clear audio transcription for folder '{folder_name}'"
|
|
143
|
+
except Exception as e:
|
|
144
|
+
return f"Error clearing audio transcription: {str(e)}"
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@mcp.tool()
|
|
148
|
+
def get_folder_clip_list(folder_path: str = "") -> Dict[str, Any]:
|
|
149
|
+
"""Get list of clips in a Media Pool folder.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
folder_path: Path from root. Empty for current folder.
|
|
153
|
+
"""
|
|
154
|
+
_, mp, err = _get_mp()
|
|
155
|
+
if err:
|
|
156
|
+
return err
|
|
157
|
+
if folder_path:
|
|
158
|
+
folder = _navigate_to_folder(mp, folder_path)
|
|
159
|
+
if not folder:
|
|
160
|
+
return {"error": f"Folder '{folder_path}' not found"}
|
|
161
|
+
else:
|
|
162
|
+
folder = mp.GetCurrentFolder()
|
|
163
|
+
clips = folder.GetClipList()
|
|
164
|
+
if clips:
|
|
165
|
+
return {"folder": folder.GetName(), "clips": [{"name": c.GetName(), "unique_id": c.GetUniqueId()} for c in clips]}
|
|
166
|
+
return {"folder": folder.GetName(), "clips": []}
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@mcp.tool()
|
|
170
|
+
def get_folder_subfolder_list(folder_path: str = "") -> Dict[str, Any]:
|
|
171
|
+
"""Get list of subfolders in a Media Pool folder.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
folder_path: Path from root. Empty for current folder.
|
|
175
|
+
"""
|
|
176
|
+
_, mp, err = _get_mp()
|
|
177
|
+
if err:
|
|
178
|
+
return err
|
|
179
|
+
if folder_path:
|
|
180
|
+
folder = _navigate_to_folder(mp, folder_path)
|
|
181
|
+
if not folder:
|
|
182
|
+
return {"error": f"Folder '{folder_path}' not found"}
|
|
183
|
+
else:
|
|
184
|
+
folder = mp.GetCurrentFolder()
|
|
185
|
+
subs = folder.GetSubFolderList()
|
|
186
|
+
if subs:
|
|
187
|
+
return {"folder": folder.GetName(), "subfolders": [{"name": s.GetName(), "unique_id": s.GetUniqueId()} for s in subs]}
|
|
188
|
+
return {"folder": folder.GetName(), "subfolders": []}
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@mcp.tool()
|
|
192
|
+
def get_folder_is_stale(folder_path: str = "") -> Dict[str, Any]:
|
|
193
|
+
"""Check if a Media Pool folder is stale (needs refresh).
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
folder_path: Path from root. Empty for current folder.
|
|
197
|
+
"""
|
|
198
|
+
_, mp, err = _get_mp()
|
|
199
|
+
if err:
|
|
200
|
+
return err
|
|
201
|
+
if folder_path:
|
|
202
|
+
folder = _navigate_to_folder(mp, folder_path)
|
|
203
|
+
if not folder:
|
|
204
|
+
return {"error": f"Folder '{folder_path}' not found"}
|
|
205
|
+
else:
|
|
206
|
+
folder = mp.GetCurrentFolder()
|
|
207
|
+
return {"folder": folder.GetName(), "is_stale": bool(folder.GetIsFolderStale())}
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@mcp.tool()
|
|
211
|
+
def get_folder_unique_id(folder_path: str = "") -> Dict[str, Any]:
|
|
212
|
+
"""Get the unique ID of a Media Pool folder.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
folder_path: Path from root. Empty for current folder.
|
|
216
|
+
"""
|
|
217
|
+
_, mp, err = _get_mp()
|
|
218
|
+
if err:
|
|
219
|
+
return err
|
|
220
|
+
if folder_path:
|
|
221
|
+
folder = _navigate_to_folder(mp, folder_path)
|
|
222
|
+
if not folder:
|
|
223
|
+
return {"error": f"Folder '{folder_path}' not found"}
|
|
224
|
+
else:
|
|
225
|
+
folder = mp.GetCurrentFolder()
|
|
226
|
+
return {"folder": folder.GetName(), "unique_id": folder.GetUniqueId()}
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@mcp.tool()
|
|
230
|
+
def folder_export(file_path: str, folder_path: str = "") -> Dict[str, Any]:
|
|
231
|
+
"""Export a Media Pool folder to a file.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
file_path: Absolute path for the export.
|
|
235
|
+
folder_path: Path from root. Empty for current folder.
|
|
236
|
+
"""
|
|
237
|
+
_, mp, err = _get_mp()
|
|
238
|
+
if err:
|
|
239
|
+
return err
|
|
240
|
+
if folder_path:
|
|
241
|
+
folder = _navigate_to_folder(mp, folder_path)
|
|
242
|
+
if not folder:
|
|
243
|
+
return {"error": f"Folder '{folder_path}' not found"}
|
|
244
|
+
else:
|
|
245
|
+
folder = mp.GetCurrentFolder()
|
|
246
|
+
result = folder.Export(file_path)
|
|
247
|
+
return {"success": bool(result), "file_path": file_path}
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
@mcp.tool()
|
|
251
|
+
def folder_transcribe_audio(folder_path: str = "") -> Dict[str, Any]:
|
|
252
|
+
"""Transcribe audio for all clips in a Media Pool folder.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
folder_path: Path from root. Empty for current folder.
|
|
256
|
+
"""
|
|
257
|
+
_, mp, err = _get_mp()
|
|
258
|
+
if err:
|
|
259
|
+
return err
|
|
260
|
+
if folder_path:
|
|
261
|
+
folder = _navigate_to_folder(mp, folder_path)
|
|
262
|
+
if not folder:
|
|
263
|
+
return {"error": f"Folder '{folder_path}' not found"}
|
|
264
|
+
else:
|
|
265
|
+
folder = mp.GetCurrentFolder()
|
|
266
|
+
result = folder.TranscribeAudio()
|
|
267
|
+
return {"success": bool(result)}
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
@mcp.tool()
|
|
271
|
+
def folder_clear_transcription(folder_path: str = "") -> Dict[str, Any]:
|
|
272
|
+
"""Clear transcription for all clips in a Media Pool folder.
|
|
273
|
+
|
|
274
|
+
Args:
|
|
275
|
+
folder_path: Path from root. Empty for current folder.
|
|
276
|
+
"""
|
|
277
|
+
_, mp, err = _get_mp()
|
|
278
|
+
if err:
|
|
279
|
+
return err
|
|
280
|
+
if folder_path:
|
|
281
|
+
folder = _navigate_to_folder(mp, folder_path)
|
|
282
|
+
if not folder:
|
|
283
|
+
return {"error": f"Folder '{folder_path}' not found"}
|
|
284
|
+
else:
|
|
285
|
+
folder = mp.GetCurrentFolder()
|
|
286
|
+
result = folder.ClearTranscription()
|
|
287
|
+
return {"success": bool(result)}
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
"""Gallery, still album, and powergrade tools."""
|
|
2
|
+
|
|
3
|
+
from src.granular.common import * # noqa: F401,F403
|
|
4
|
+
|
|
5
|
+
resolve = ResolveProxy()
|
|
6
|
+
|
|
7
|
+
@mcp.tool(annotations=READ_ONLY_TOOL)
|
|
8
|
+
def get_gallery_album_name() -> Dict[str, Any]:
|
|
9
|
+
"""Get the name of the current gallery album."""
|
|
10
|
+
resolve = get_resolve()
|
|
11
|
+
if resolve is None:
|
|
12
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
13
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
14
|
+
if not project:
|
|
15
|
+
return {"error": "No project open"}
|
|
16
|
+
gallery = project.GetGallery()
|
|
17
|
+
if not gallery:
|
|
18
|
+
return {"error": "Failed to get Gallery"}
|
|
19
|
+
name = gallery.GetAlbumName()
|
|
20
|
+
return {"album_name": name if name else ""}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@mcp.tool()
|
|
24
|
+
def set_gallery_album_name(name: str) -> Dict[str, Any]:
|
|
25
|
+
"""Set the name of the current gallery album.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
name: New album name.
|
|
29
|
+
"""
|
|
30
|
+
resolve = get_resolve()
|
|
31
|
+
if resolve is None:
|
|
32
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
33
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
34
|
+
if not project:
|
|
35
|
+
return {"error": "No project open"}
|
|
36
|
+
gallery = project.GetGallery()
|
|
37
|
+
if not gallery:
|
|
38
|
+
return {"error": "Failed to get Gallery"}
|
|
39
|
+
result = gallery.SetAlbumName(name)
|
|
40
|
+
return {"success": bool(result)}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@mcp.tool()
|
|
44
|
+
def get_gallery_still_albums() -> Dict[str, Any]:
|
|
45
|
+
"""Get list of all gallery still albums."""
|
|
46
|
+
resolve = get_resolve()
|
|
47
|
+
if resolve is None:
|
|
48
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
49
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
50
|
+
if not project:
|
|
51
|
+
return {"error": "No project open"}
|
|
52
|
+
gallery = project.GetGallery()
|
|
53
|
+
if not gallery:
|
|
54
|
+
return {"error": "Failed to get Gallery"}
|
|
55
|
+
albums = gallery.GetGalleryStillAlbums()
|
|
56
|
+
return {"albums": [str(a) for a in albums] if albums else []}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@mcp.tool()
|
|
60
|
+
def get_gallery_power_grade_albums() -> Dict[str, Any]:
|
|
61
|
+
"""Get list of all gallery power grade albums."""
|
|
62
|
+
resolve = get_resolve()
|
|
63
|
+
if resolve is None:
|
|
64
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
65
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
66
|
+
if not project:
|
|
67
|
+
return {"error": "No project open"}
|
|
68
|
+
gallery = project.GetGallery()
|
|
69
|
+
if not gallery:
|
|
70
|
+
return {"error": "Failed to get Gallery"}
|
|
71
|
+
albums = gallery.GetGalleryPowerGradeAlbums()
|
|
72
|
+
return {"albums": [str(a) for a in albums] if albums else []}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@mcp.tool()
|
|
76
|
+
def get_current_still_album() -> Dict[str, Any]:
|
|
77
|
+
"""Get the current still album."""
|
|
78
|
+
resolve = get_resolve()
|
|
79
|
+
if resolve is None:
|
|
80
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
81
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
82
|
+
if not project:
|
|
83
|
+
return {"error": "No project open"}
|
|
84
|
+
gallery = project.GetGallery()
|
|
85
|
+
if not gallery:
|
|
86
|
+
return {"error": "Failed to get Gallery"}
|
|
87
|
+
album = gallery.GetCurrentStillAlbum()
|
|
88
|
+
return {"has_album": album is not None}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@mcp.tool()
|
|
92
|
+
def set_current_still_album(album_index: int) -> Dict[str, Any]:
|
|
93
|
+
"""Set the current still album by index.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
album_index: 0-based index of the album in GetGalleryStillAlbums() list.
|
|
97
|
+
"""
|
|
98
|
+
resolve = get_resolve()
|
|
99
|
+
if resolve is None:
|
|
100
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
101
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
102
|
+
if not project:
|
|
103
|
+
return {"error": "No project open"}
|
|
104
|
+
gallery = project.GetGallery()
|
|
105
|
+
if not gallery:
|
|
106
|
+
return {"error": "Failed to get Gallery"}
|
|
107
|
+
albums = gallery.GetGalleryStillAlbums()
|
|
108
|
+
if not albums or album_index >= len(albums):
|
|
109
|
+
return {"error": f"No album at index {album_index}"}
|
|
110
|
+
result = gallery.SetCurrentStillAlbum(albums[album_index])
|
|
111
|
+
return {"success": bool(result)}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@mcp.tool()
|
|
115
|
+
def create_gallery_still_album(album_name: str = "") -> Dict[str, Any]:
|
|
116
|
+
"""Create a new gallery still album.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
album_name: Optional name for the new album.
|
|
120
|
+
"""
|
|
121
|
+
resolve = get_resolve()
|
|
122
|
+
if resolve is None:
|
|
123
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
124
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
125
|
+
if not project:
|
|
126
|
+
return {"error": "No project open"}
|
|
127
|
+
gallery = project.GetGallery()
|
|
128
|
+
if not gallery:
|
|
129
|
+
return {"error": "Failed to get Gallery"}
|
|
130
|
+
if album_name:
|
|
131
|
+
album = gallery.CreateGalleryStillAlbum(album_name)
|
|
132
|
+
else:
|
|
133
|
+
album = gallery.CreateGalleryStillAlbum()
|
|
134
|
+
return {"success": album is not None}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@mcp.tool()
|
|
138
|
+
def create_gallery_power_grade_album(album_name: str = "") -> Dict[str, Any]:
|
|
139
|
+
"""Create a new gallery power grade album.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
album_name: Optional name for the new album.
|
|
143
|
+
"""
|
|
144
|
+
resolve = get_resolve()
|
|
145
|
+
if resolve is None:
|
|
146
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
147
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
148
|
+
if not project:
|
|
149
|
+
return {"error": "No project open"}
|
|
150
|
+
gallery = project.GetGallery()
|
|
151
|
+
if not gallery:
|
|
152
|
+
return {"error": "Failed to get Gallery"}
|
|
153
|
+
if album_name:
|
|
154
|
+
album = gallery.CreateGalleryPowerGradeAlbum(album_name)
|
|
155
|
+
else:
|
|
156
|
+
album = gallery.CreateGalleryPowerGradeAlbum()
|
|
157
|
+
return {"success": album is not None}
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@mcp.tool()
|
|
161
|
+
def get_album_stills(album_index: int = 0) -> Dict[str, Any]:
|
|
162
|
+
"""Get list of stills in a gallery album.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
album_index: 0-based index of the album. Default: 0.
|
|
166
|
+
"""
|
|
167
|
+
resolve = get_resolve()
|
|
168
|
+
if resolve is None:
|
|
169
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
170
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
171
|
+
if not project:
|
|
172
|
+
return {"error": "No project open"}
|
|
173
|
+
gallery = project.GetGallery()
|
|
174
|
+
if not gallery:
|
|
175
|
+
return {"error": "Failed to get Gallery"}
|
|
176
|
+
albums = gallery.GetGalleryStillAlbums()
|
|
177
|
+
if not albums or album_index >= len(albums):
|
|
178
|
+
return {"error": f"No album at index {album_index}"}
|
|
179
|
+
stills = albums[album_index].GetStills()
|
|
180
|
+
return {"still_count": len(stills) if stills else 0}
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
@mcp.tool()
|
|
184
|
+
def get_still_label(album_index: int, still_index: int) -> Dict[str, Any]:
|
|
185
|
+
"""Get the label of a still in a gallery album.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
album_index: 0-based album index.
|
|
189
|
+
still_index: 0-based still index.
|
|
190
|
+
"""
|
|
191
|
+
resolve = get_resolve()
|
|
192
|
+
if resolve is None:
|
|
193
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
194
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
195
|
+
if not project:
|
|
196
|
+
return {"error": "No project open"}
|
|
197
|
+
gallery = project.GetGallery()
|
|
198
|
+
albums = gallery.GetGalleryStillAlbums()
|
|
199
|
+
if not albums or album_index >= len(albums):
|
|
200
|
+
return {"error": f"No album at index {album_index}"}
|
|
201
|
+
stills = albums[album_index].GetStills()
|
|
202
|
+
if not stills or still_index >= len(stills):
|
|
203
|
+
return {"error": f"No still at index {still_index}"}
|
|
204
|
+
label = albums[album_index].GetLabel(stills[still_index])
|
|
205
|
+
return {"label": label if label else ""}
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
@mcp.tool()
|
|
209
|
+
def set_still_label(album_index: int, still_index: int, label: str) -> Dict[str, Any]:
|
|
210
|
+
"""Set the label of a still in a gallery album.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
album_index: 0-based album index.
|
|
214
|
+
still_index: 0-based still index.
|
|
215
|
+
label: New label for the still.
|
|
216
|
+
"""
|
|
217
|
+
resolve = get_resolve()
|
|
218
|
+
if resolve is None:
|
|
219
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
220
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
221
|
+
if not project:
|
|
222
|
+
return {"error": "No project open"}
|
|
223
|
+
gallery = project.GetGallery()
|
|
224
|
+
albums = gallery.GetGalleryStillAlbums()
|
|
225
|
+
if not albums or album_index >= len(albums):
|
|
226
|
+
return {"error": f"No album at index {album_index}"}
|
|
227
|
+
stills = albums[album_index].GetStills()
|
|
228
|
+
if not stills or still_index >= len(stills):
|
|
229
|
+
return {"error": f"No still at index {still_index}"}
|
|
230
|
+
result = albums[album_index].SetLabel(stills[still_index], label)
|
|
231
|
+
return {"success": bool(result)}
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
@mcp.tool()
|
|
235
|
+
def import_stills_to_album(album_index: int, file_paths: List[str]) -> Dict[str, Any]:
|
|
236
|
+
"""Import stills from file paths into a gallery album.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
album_index: 0-based album index.
|
|
240
|
+
file_paths: List of absolute file paths to import.
|
|
241
|
+
"""
|
|
242
|
+
resolve = get_resolve()
|
|
243
|
+
if resolve is None:
|
|
244
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
245
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
246
|
+
if not project:
|
|
247
|
+
return {"error": "No project open"}
|
|
248
|
+
gallery = project.GetGallery()
|
|
249
|
+
albums = gallery.GetGalleryStillAlbums()
|
|
250
|
+
if not albums or album_index >= len(albums):
|
|
251
|
+
return {"error": f"No album at index {album_index}"}
|
|
252
|
+
result = albums[album_index].ImportStills(file_paths)
|
|
253
|
+
return {"success": bool(result)}
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
@mcp.tool()
|
|
257
|
+
def export_stills_from_album(album_index: int, folder_path: str, file_prefix: str = "still", format: str = "dpx") -> Dict[str, Any]:
|
|
258
|
+
"""Export stills from a gallery album.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
album_index: 0-based album index.
|
|
262
|
+
folder_path: Directory to export to.
|
|
263
|
+
file_prefix: Filename prefix. Default: 'still'.
|
|
264
|
+
format: File format (dpx, cin, tif, jpg, png, ppm, bmp, xpm, drx). Default: 'dpx'.
|
|
265
|
+
"""
|
|
266
|
+
resolve = get_resolve()
|
|
267
|
+
if resolve is None:
|
|
268
|
+
return {"error": "Not connected to DaVinci Resolve"}
|
|
269
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
270
|
+
if not project:
|
|
271
|
+
return {"error": "No project open"}
|
|
272
|
+
gallery = project.GetGallery()
|
|
273
|
+
albums = gallery.GetGalleryStillAlbums()
|
|
274
|
+
if not albums or album_index >= len(albums):
|
|
275
|
+
return {"error": f"No album at index {album_index}"}
|
|
276
|
+
stills = albums[album_index].GetStills()
|
|
277
|
+
if not stills:
|
|
278
|
+
return {"error": "No stills in album"}
|
|
279
|
+
result = albums[album_index].ExportStills(stills, folder_path, file_prefix, format)
|
|
280
|
+
return {"success": bool(result)}
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
@mcp.tool()
|
|
284
|
+
def delete_stills_from_album(album_index: int, still_indices: List[int]) -> Dict[str, Any]:
|
|
285
|
+
"""Delete stills from a gallery album.
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
album_index: 0-based album index.
|
|
289
|
+
still_indices: List of 0-based still indices to delete.
|
|
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
|
+
gallery = project.GetGallery()
|
|
298
|
+
albums = gallery.GetGalleryStillAlbums()
|
|
299
|
+
if not albums or album_index >= len(albums):
|
|
300
|
+
return {"error": f"No album at index {album_index}"}
|
|
301
|
+
stills = albums[album_index].GetStills()
|
|
302
|
+
if not stills:
|
|
303
|
+
return {"error": "No stills in album"}
|
|
304
|
+
to_delete = [stills[i] for i in still_indices if i < len(stills)]
|
|
305
|
+
result = albums[album_index].DeleteStills(to_delete)
|
|
306
|
+
return {"success": bool(result)}
|