DaVinci MCP Professional v3.0.0
A modern, professional Model Context Protocol server for DaVinci Resolve integration
Loading...
Searching...
No Matches
resolve_client.py
Go to the documentation of this file.
1"""
2DaVinci Resolve client wrapper.
3
4Provides a clean interface to the DaVinci Resolve API with proper error handling
5and logging.
6"""
7
8import logging
9from typing import Any, Dict, List, Optional, TYPE_CHECKING
10
11if TYPE_CHECKING:
12 # Import for type checking only to avoid runtime import issues
13 from .types import DaVinciResolveApp, DaVinciProjectManager, DaVinciProject
14
15from .utils.platform import setup_resolve_environment, check_resolve_running
16
17
18logger = logging.getLogger(__name__)
19
20
21class DaVinciResolveError(Exception):
22 """Base exception for DaVinci Resolve related errors."""
23
24 pass
25
26
28 """Raised when DaVinci Resolve is not running."""
29
30 pass
31
32
34 """Raised when connection to DaVinci Resolve fails."""
35
36 pass
37
38
40 """
41 A clean interface to the DaVinci Resolve API.
42
43 This class handles the connection to DaVinci Resolve and provides
44 organized methods for interacting with projects, timelines, media, etc.
45 """
46
47 def __init__(self) -> None:
48 self._resolve: Optional["DaVinciResolveApp"] = None
49 self._project_manager: Optional["DaVinciProjectManager"] = None
50 self._current_project: Optional["DaVinciProject"] = None
51 self._is_connected = False
52
53 def connect(self) -> None:
54 """Connect to DaVinci Resolve."""
55 # Check if Resolve is running
56 if not check_resolve_running():
58 "DaVinci Resolve is not running. Please start DaVinci Resolve first."
59 )
60
61 # Set up environment
62 if not setup_resolve_environment():
64 "Failed to set up DaVinci Resolve environment variables."
65 )
66
67 try:
68 # Import and connect to Resolve
69 import DaVinciResolveScript as dvr_script # type: ignore[reportMissingImports]
70
71 self._resolve = dvr_script.scriptapp("Resolve") # type: ignore[reportUnknownMemberType]
72
73 if self._resolve is None:
75 "Failed to get Resolve object. Check that DaVinci Resolve is running."
76 ) # type: ignore[reportUnknownMemberType]
77
78 # Get project manager
79 self._project_manager = self._resolve.GetProjectManager() # type: ignore[reportUnknownMemberType]
80 if self._project_manager is None:
81 raise DaVinciResolveConnectionError("Failed to get Project Manager.")
82
83 # Get current project if one is open
84 self._current_project = self._project_manager.GetCurrentProject() # type: ignore[reportUnknownMemberType]
85
86 self._is_connected = True
87 logger.info(f"Connected to {self.get_version()}")
88
89 except ImportError as e:
91 f"Failed to import DaVinciResolveScript: {e}. "
92 "Check environment variables and DaVinci Resolve installation."
93 )
94 except Exception as e:
95 raise DaVinciResolveConnectionError(f"Unexpected error connecting: {e}")
96
97 def disconnect(self) -> None:
98 """Disconnect from DaVinci Resolve."""
99 self._resolve = None
100 self._project_manager = None
101 self._current_project = None
102 self._is_connected = False
103 logger.info("Disconnected from DaVinci Resolve")
104
105 def is_connected(self) -> bool:
106 """Check if connected to DaVinci Resolve."""
107 return self._is_connected
108
109 def _ensure_connected(self) -> None:
110 """Ensure we're connected to Resolve."""
111 if not self._is_connected:
112 raise DaVinciResolveConnectionError("Not connected to DaVinci Resolve")
113
114 def _ensure_project(self) -> Any:
115 """Ensure we have a current project."""
116 self._ensure_connected()
117
118 # Refresh current project
119 if self._project_manager:
120 self._current_project = self._project_manager.GetCurrentProject()
121
122 if self._current_project is None:
123 raise DaVinciResolveError("No project is currently open")
124
125 return self._current_project
126
127 # System Information
128 def get_version(self) -> str:
129 """Get DaVinci Resolve version."""
130 self._ensure_connected()
131 if self._resolve:
132 return (
133 f"{self._resolve.GetProductName()} {self._resolve.GetVersionString()}"
134 )
135 return "Unknown"
136
137 def get_current_page(self) -> str:
138 """Get the current page (Edit, Color, Fusion, etc.)."""
139 self._ensure_connected()
140 if self._resolve:
141 return self._resolve.GetCurrentPage()
142 return "Unknown"
143
144 def switch_page(self, page: str) -> bool:
145 """Switch to a specific page."""
146 self._ensure_connected()
147
148 valid_pages = [
149 "media",
150 "cut",
151 "edit",
152 "fusion",
153 "color",
154 "fairlight",
155 "deliver",
156 ]
157 if page.lower() not in valid_pages:
158 raise ValueError(f"Invalid page. Must be one of: {', '.join(valid_pages)}")
159
160 if self._resolve:
161 return bool(self._resolve.OpenPage(page.lower()))
162 return False
163
164 # Project Management
165 def list_projects(self) -> List[str]:
166 """List all projects in the current database."""
167 self._ensure_connected()
168
169 if self._project_manager:
170 projects = self._project_manager.GetProjectListInCurrentFolder()
171 return [p for p in projects if p] # Filter out empty strings
172 return []
173
174 def get_current_project_name(self) -> Optional[str]:
175 """Get the name of the currently open project."""
176 try:
177 project = self._ensure_project()
178 return project.GetName()
179 except DaVinciResolveError:
180 return None
181
182 def open_project(self, name: str) -> bool:
183 """Open a project by name."""
184 self._ensure_connected()
185
186 if not self._project_manager:
187 return False
188
189 # Check if project exists
190 projects = self.list_projects()
191 if name not in projects:
192 raise ValueError(
193 f"Project '{name}' not found. Available: {', '.join(projects)}"
194 )
195
196 result = self._project_manager.LoadProject(name)
197 if result:
198 self._current_project = self._project_manager.GetCurrentProject()
199 logger.info(f"Opened project: {name}")
200
201 return bool(result)
202
203 def create_project(self, name: str) -> bool:
204 """Create a new project."""
205 self._ensure_connected()
206
207 if not self._project_manager:
208 return False
209
210 # Check if project already exists
211 projects = self.list_projects()
212 if name in projects:
213 raise ValueError(f"Project '{name}' already exists")
214
215 result = self._project_manager.CreateProject(name)
216 if result:
217 self._current_project = self._project_manager.GetCurrentProject()
218 logger.info(f"Created project: {name}")
219
220 return bool(result)
221
222 # Timeline Management
223 def list_timelines(self) -> List[str]:
224 """List all timelines in the current project."""
225 project = self._ensure_project()
226
227 timeline_count = project.GetTimelineCount()
228 timelines: List[str] = []
229
230 for i in range(1, timeline_count + 1):
231 timeline = project.GetTimelineByIndex(i)
232 if timeline:
233 name = timeline.GetName()
234 if isinstance(name, str):
235 timelines.append(name)
236
237 return timelines
238
239 def get_current_timeline_name(self) -> Optional[str]:
240 """Get the name of the current timeline."""
241 try:
242 project = self._ensure_project()
243 current_timeline = project.GetCurrentTimeline()
244 return current_timeline.GetName() if current_timeline else None
245 except DaVinciResolveError:
246 return None
247
248 def create_timeline(self, name: str) -> bool:
249 """Create a new timeline."""
250 project = self._ensure_project()
251
252 media_pool = project.GetMediaPool()
253 if not media_pool:
254 raise DaVinciResolveError("Failed to get Media Pool")
255
256 timeline = media_pool.CreateEmptyTimeline(name)
257 if timeline:
258 logger.info(f"Created timeline: {name}")
259 return True
260
261 return False
262
263 def switch_timeline(self, name: str) -> bool:
264 """Switch to a timeline by name."""
265 project = self._ensure_project()
266
267 # Find timeline by name
268 timeline_count = project.GetTimelineCount()
269 for i in range(1, timeline_count + 1):
270 timeline = project.GetTimelineByIndex(i)
271 if timeline and timeline.GetName() == name:
272 result = project.SetCurrentTimeline(timeline)
273 if result:
274 logger.info(f"Switched to timeline: {name}")
275 return bool(result)
276
277 raise ValueError(f"Timeline '{name}' not found")
278
279 # Media Pool Management
280 def list_media_clips(self) -> List[Dict[str, Any]]:
281 """List all clips in the media pool root folder."""
282 project = self._ensure_project()
283
284 media_pool = project.GetMediaPool()
285 if not media_pool:
286 raise DaVinciResolveError("Failed to get Media Pool")
287
288 root_folder = media_pool.GetRootFolder()
289 if not root_folder:
290 raise DaVinciResolveError("Failed to get root folder")
291
292 clips = root_folder.GetClipList()
293 if not clips:
294 return []
295
296 result: List[Dict[str, Any]] = []
297 for clip in clips:
298 clip_info = {
299 "name": clip.GetName(),
300 "duration": clip.GetDuration(),
301 "fps": clip.GetClipProperty("FPS") or "Unknown",
302 }
303 result.append(clip_info)
304
305 return result
306
307 def import_media(self, file_path: str) -> bool:
308 """Import a media file into the media pool."""
309 project = self._ensure_project()
310
311 media_pool = project.GetMediaPool()
312 if not media_pool:
313 raise DaVinciResolveError("Failed to get Media Pool")
314
315 # Import the media file
316 imported_clips = media_pool.ImportMedia([file_path])
317
318 if imported_clips:
319 logger.info(f"Imported media: {file_path}")
320 return True
321
322 return False