#
tokens: 24859/50000 2/85 files (page 4/5)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 4 of 5. Use http://codebase.md/samuelgursky/davinci-resolve-mcp?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .cursorrules
├── .gitignore
├── CHANGELOG.md
├── CHANGES.md
├── config
│   ├── cursor-mcp-example.json
│   ├── macos
│   │   ├── claude-desktop-config.template.json
│   │   └── cursor-mcp-config.template.json
│   ├── mcp-project-template.json
│   ├── README.md
│   ├── sample_config.json
│   └── windows
│       ├── claude-desktop-config.template.json
│       └── cursor-mcp-config.template.json
├── docs
│   ├── CHANGELOG.md
│   ├── COMMIT_MESSAGE.txt
│   ├── FEATURES.md
│   ├── PROJECT_MCP_SETUP.md
│   ├── TOOLS_README.md
│   └── VERSION.md
├── examples
│   ├── getting_started.py
│   ├── markers
│   │   ├── add_spaced_markers.py
│   │   ├── add_timecode_marker.py
│   │   ├── alternating_markers.py
│   │   ├── clear_add_markers.py
│   │   ├── README.md
│   │   └── test_marker_frames.py
│   ├── media
│   │   ├── import_folder.py
│   │   └── README.md
│   ├── README.md
│   └── timeline
│       ├── README.md
│       ├── timeline_check.py
│       └── timeline_info.py
├── INSTALL.md
├── LICENSE
├── logs
│   └── .gitkeep
├── README.md
├── requirements.txt
├── resolve_mcp_server.py
├── run-now.bat
├── run-now.sh
├── scripts
│   ├── batch_automation.py
│   ├── check-resolve-ready.bat
│   ├── check-resolve-ready.ps1
│   ├── check-resolve-ready.sh
│   ├── create_app_shortcut.sh
│   ├── create-release-zip.bat
│   ├── create-release-zip.sh
│   ├── launch.sh
│   ├── mcp_resolve_launcher.sh
│   ├── mcp_resolve-claude_start
│   ├── mcp_resolve-cursor_start
│   ├── README.md
│   ├── resolve_mcp_server.py
│   ├── restart-server.bat
│   ├── restart-server.sh
│   ├── run-now.bat
│   ├── run-now.sh
│   ├── run-server.sh
│   ├── server.sh
│   ├── setup
│   │   ├── install.bat
│   │   └── install.sh
│   ├── setup.sh
│   ├── utils.sh
│   ├── verify-installation.bat
│   └── verify-installation.sh
├── src
│   ├── __init__.py
│   ├── api
│   │   ├── __init__.py
│   │   ├── color_operations.py
│   │   ├── delivery_operations.py
│   │   ├── media_operations.py
│   │   ├── project_operations.py
│   │   └── timeline_operations.py
│   ├── bin
│   │   └── __init__.py
│   ├── main.py
│   ├── resolve_mcp_server.py
│   └── utils
│       ├── __init__.py
│       ├── app_control.py
│       ├── cloud_operations.py
│       ├── layout_presets.py
│       ├── object_inspection.py
│       ├── platform.py
│       ├── project_properties.py
│       └── resolve_connection.py
└── tests
    ├── benchmark_server.py
    ├── create_test_timeline.py
    ├── test_custom_timeline.py
    ├── test_improvements.py
    ├── test-after-restart.bat
    └── test-after-restart.sh
```

# Files

--------------------------------------------------------------------------------
/docs/FEATURES.md:
--------------------------------------------------------------------------------

```markdown
  1 | # DaVinci Resolve MCP Server Features
  2 | 
  3 | This document tracks the implementation status of features in the DaVinci Resolve MCP (Multi-Client Protocol) Server. It is organized by feature categories and provides details on implementation status, compatibility with clients, and any known issues.
  4 | 
  5 | ## Implementation Status
  6 | 
  7 | The MCP server implements nearly all features from the DaVinci Resolve scripting API, but our testing has revealed that while we have implemented 202 features (100%), only a small percentage have been verified working on macOS (8%), with many features still needing verification (82%) or having known issues (10%).
  8 | 
  9 | Testing has primarily been conducted on macOS, with Windows support implemented but requiring thorough testing. Each feature in this document is marked with symbols indicating its current status:
 10 | 
 11 | **Status Key:**
 12 | - ✅ - Implemented and verified working
 13 | - ⚠️ - Implemented but needs testing/verification
 14 | - 🐞 - Implemented but has known issues
 15 | - 🟡 - Planned feature
 16 | - 🚫 - Not implemented/supported
 17 | 
 18 | The compatibility columns indicate whether a feature is known to work with specific clients (Cursor/Claude) on specific platforms (Mac/Windows).
 19 | 
 20 | ## Feature Categories
 21 | 
 22 | ## Status Definitions
 23 | 
 24 | ✅ - **Implemented & Verified**: Feature is fully implemented and verified working  
 25 | ⚠️ - **Implemented with Limitations**: Feature works but has known limitations or requirements  
 26 | 🔄 - **In progress**: Feature is in development or testing phase  
 27 | 🟡 - **Planned**: Feature is planned but not yet implemented  
 28 | ❌ - **Not implemented**: Feature will not be implemented  
 29 | 🚫 - **Not applicable**: Feature is not applicable to the current platform  
 30 | 🐞 - **Implementation Issues**: Feature is implemented but has known bugs  
 31 | 
 32 | ## Client/Platform Compatibility Update
 33 | 
 34 | | Client | macOS | Windows | Linux |
 35 | |--------|-------|---------|-------|
 36 | | Cursor | ✅ Stable | ⚠️ Needs Testing | ❌ |
 37 | | Claude Desktop | ✅ Stable | ⚠️ Needs Testing | ❌ |
 38 | 
 39 | ## Implementation Methods
 40 | 
 41 | | Method | Status | Notes |
 42 | |--------|--------|-------|
 43 | | MCP Framework | 🐞 | Original implementation - connection issues |
 44 | | Direct JSON-RPC | ✅ | Current implementation - more reliable |
 45 | 
 46 | ## Feature Statistics
 47 | 
 48 | | Category | Total Features | Implemented | Verified (Mac) | Verified (Win) | Not Verified | Failed |
 49 | |----------|----------------|-------------|----------------|----------------|--------------|--------|
 50 | | Core Features | 9 | 9 (100%) | 4 (44%) | 0 (0%) | 3 (33%) | 2 (22%) |
 51 | | General Resolve API | 14 | 14 (100%) | 6 (43%) | 0 (0%) | 5 (36%) | 3 (21%) |
 52 | | Project Management | 18 | 18 (100%) | 2 (11%) | 0 (0%) | 15 (83%) | 1 (6%) |
 53 | | Timeline Operations | 12 | 12 (100%) | 2 (17%) | 0 (0%) | 8 (67%) | 2 (16%) |
 54 | | Media Pool Operations | 18 | 18 (100%) | 0 (0%) | 0 (0%) | 16 (89%) | 2 (11%) |
 55 | | Color Page Operations | 16 | 16 (100%) | 0 (0%) | 0 (0%) | 14 (88%) | 2 (12%) |
 56 | | Delivery Page Operations | 12 | 12 (100%) | 1 (8%) | 0 (0%) | 10 (84%) | 1 (8%) |
 57 | | Fusion Page Operations | 0 | 0 (0%) | 0 (0%) | 0 (0%) | 0 (0%) | 0 (0%) |
 58 | | Fairlight Page Operations | 0 | 0 (0%) | 0 (0%) | 0 (0%) | 0 (0%) | 0 (0%) |
 59 | | Media Storage Operations | 0 | 0 (0%) | 0 (0%) | 0 (0%) | 0 (0%) | 0 (0%) |
 60 | | Audio Sync | 4 | 4 (100%) | 0 (0%) | 0 (0%) | 4 (100%) | 0 (0%) |
 61 | | Cache Management | 3 | 3 (100%) | 1 (33%) | 0 (0%) | 2 (67%) | 0 (0%) |
 62 | | Proxy Media Management | 6 | 6 (100%) | 0 (0%) | 0 (0%) | 5 (83%) | 1 (17%) |
 63 | | Transcription Services | 6 | 6 (100%) | 0 (0%) | 0 (0%) | 5 (83%) | 1 (17%) |
 64 | | Object Methods | 84 | 84 (100%) | 1 (1%) | 0 (0%) | 79 (94%) | 4 (5%) |
 65 | | **TOTAL** | **202** | **202 (100%)** | **17 (8%)** | **0 (0%)** | **166 (82%)** | **19 (10%)** |
 66 | 
 67 | **Status Key:**
 68 | - ✅ - Implemented and verified working
 69 | - ⚠️ - Implemented but needs testing/verification
 70 | - 🐞 - Implemented but has known issues
 71 | - 🟡 - Planned feature
 72 | - 🚫 - Not implemented/supported
 73 | 
 74 | ## Core Features
 75 | 
 76 | | Feature | Implementation | Cursor (Mac) | Claude (Mac) | Cursor (Win) | Claude (Win) | Notes |
 77 | |---------|---------------|--------------|--------------|--------------|--------------|-------|
 78 | | Connect to Resolve | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Establish connection to DaVinci Resolve |
 79 | | Switch to Page | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Switch between Media, Edit, Color, etc. - Verified working |
 80 | | Get Current Page | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Get current active page |
 81 | | Get Resolve Version | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Get DaVinci Resolve version |
 82 | | Get Product Name | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Get product name (Studio or free) |
 83 | | Object Inspection | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Introspect API objects, methods, and properties |
 84 | | Error Handling | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Error messages exist but could be more informative |
 85 | 
 86 | ### Project Management
 87 | 
 88 | | Feature | Implementation | Cursor (Mac) | Claude (Mac) | Cursor (Win) | Claude (Win) | Notes |
 89 | |---------|---------------|--------------|--------------|--------------|--------------|-------|
 90 | | List Projects | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Get list of available projects |
 91 | | Get Current Project Name | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Get name of currently open project |
 92 | | Open Project | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Open project by name - Verified working |
 93 | | Create Project | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Create new project - Cannot recreate existing projects |
 94 | | Save Project | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Save current project |
 95 | | Close Project | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Close current project |
 96 | | Project Properties | 🐞 | 🐞 | 🐞 | ⚠️ | ⚠️ | Get and set project settings - Parameter type issues |
 97 | | SuperScale Settings | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Control super scale quality - Not verified |
 98 | | Timeline Frame Rate | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Control timeline frame rates - Not verified |
 99 | | Export/Import Project | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Import/export project files - Not verified |
100 | | Archive Project | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Archive projects with media - Not verified |
101 | | Cloud Project Operations | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Create and manage cloud projects - Not verified |
102 | | Project Folders | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Create and navigate project folders - Not verified |
103 | | Project Presets | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Apply and manage project presets - Not verified |
104 | | Load Time/Performance | 🟡 | - | - | - | - | Project load time and performance metrics |
105 | | Project Analytics | 🟡 | - | - | - | - | Project usage and statistics |
106 | | Collaborative Projects | 🟡 | - | - | - | - | Manage collaborative workflows |
107 | | Database Management | 🟡 | - | - | - | - | PostgreSQL and local database operations |
108 | | Project Templates | 🟡 | - | - | - | - | Save and load project templates |
109 | 
110 | ### Timeline Operations
111 | 
112 | | Feature | Implementation | Cursor (Mac) | Claude (Mac) | Cursor (Win) | Claude (Win) | Notes |
113 | |---------|---------------|--------------|--------------|--------------|--------------|-------|
114 | | Create Timeline | 🐞 | 🐞 | 🐞 | ⚠️ | ⚠️ | Create timeline - Failed with existing names without clear error |
115 | | List Timelines | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Get all timelines in project - Verified working |
116 | | Get Current Timeline | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Get current active timeline |
117 | | Set Current Timeline | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Switch to specified timeline - Verified working |
118 | | Add Timeline Marker | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Add marker at position - Requires valid frame within timeline bounds |
119 | | Delete Timeline Marker | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Delete marker at position - Not verified |
120 | | Manage Timeline Tracks | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Add/remove video and audio tracks - Not verified |
121 | | Get Timeline Items | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Get clips in timeline - Not verified |
122 | | Timecode Operations | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Get/set current timecode - Not verified |
123 | | Timeline Settings | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Manage timeline settings - Not verified |
124 | | Timeline Generators | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Insert generators into timeline - Not verified |
125 | | Timeline OFX | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Insert OFX plugins into timeline - Not verified |
126 | | Timeline Import/Export | 🟡 | - | - | - | - | Import/export timeline formats |
127 | | Scene Detection | 🟡 | - | - | - | - | Detect scene cuts automatically |
128 | | Auto Subtitle Creation | 🟡 | - | - | - | - | Generate subtitles from audio |
129 | 
130 | ### Media Pool Operations
131 | 
132 | | Feature | Implementation | Cursor (Mac) | Claude (Mac) | Cursor (Win) | Claude (Win) | Notes |
133 | |---------|---------------|--------------|--------------|--------------|--------------|-------|
134 | | Import Media | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Import media files |
135 | | List Media | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | List media pool clips |
136 | | Create Bins | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Create folders in media pool - Verified working |
137 | | Organize Media | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Move clips between folders |
138 | | Add to Timeline | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Add clips to timeline |
139 | | Clip Properties | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Get/set clip properties |
140 | | Clip Markers | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Add/manage clip markers |
141 | | Metadata Management | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Get/set clip metadata |
142 | | Media Relinking | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Relink/unlink media files |
143 | | Audio Sync | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Sync audio between clips |
144 | | Proxy Media | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Link/unlink proxy media |
145 | | Clip Transcription | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Transcribe audio in clips |
146 | | Bulk Import | 🟡 | - | - | - | - | Batch import operations |
147 | | Smart Bins | 🟡 | - | - | - | - | Create/manage smart bins |
148 | 
149 | ### Media Storage Operations
150 | 
151 | | Feature | Implementation | Cursor (Mac) | Claude (Mac) | Cursor (Win) | Claude (Win) | Notes |
152 | |---------|---------------|--------------|--------------|--------------|--------------|-------|
153 | | Get Mounted Volumes | 🟡 | - | - | - | - | List mounted storage devices |
154 | | Browse Folders | 🟡 | - | - | - | - | Navigate folder structure |
155 | | List Media Files | 🟡 | - | - | - | - | List media in folders |
156 | | Reveal in Storage | 🟡 | - | - | - | - | Highlight file in browser |
157 | 
158 | ### Color Page Operations
159 | 
160 | | Feature | Implementation | Cursor (Mac) | Claude (Mac) | Cursor (Win) | Claude (Win) | Notes |
161 | |---------|---------------|--------------|--------------|--------------|--------------|-------|
162 | | Apply LUTs | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Apply LUTs to clips |
163 | | Color Correction | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Adjust color parameters |
164 | | Get/Set Grades | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Manage color grades |
165 | | Node Management | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Work with node graph - Note: May require clips with existing grade objects |
166 | | Gallery Operations | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Save/load looks from gallery |
167 | | Color Wheels | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Adjust lift/gamma/gain - Note: Requires clips with existing grade objects |
168 | | Grade Versions | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Manage color versions |
169 | | Export Grades | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Export grades as files |
170 | | Color Groups | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Group clips for color |
171 | | Node Cache | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Control node caching |
172 | | Flag Management | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Add/remove clip flags |
173 | | Color Space | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Color space controls |
174 | | Magic Mask | 🟡 | - | - | - | - | AI-based masking |
175 | | Track/Window | 🟡 | - | - | - | - | Motion tracking operations |
176 | | HDR Grading | 🟡 | - | - | - | - | High dynamic range controls |
177 | | Face Refinement | 🟡 | - | - | - | - | Automated face enhancement |
178 | 
179 | ### Delivery Page Operations
180 | 
181 | | Feature | Implementation | Cursor (Mac) | Claude (Mac) | Cursor (Win) | Claude (Win) | Notes |
182 | |---------|---------------|--------------|--------------|--------------|--------------|-------|
183 | | Add Render Job | 🐞 | 🐞 | 🐞 | ⚠️ | ⚠️ | Add job to render queue - Failed with "'NoneType' object is not callable" |
184 | | Start Rendering | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Start render process - Not verified |
185 | | List Render Jobs | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Get all queued render jobs - Not verified |
186 | | Delete Render Jobs | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Remove jobs from queue - Not verified |
187 | | Clear Render Queue | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Clear render queue - Verified working |
188 | | Get Render Presets | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | List available presets - Not verified |
189 | | Render Status | 🟡 | - | - | - | - | Check render progress |
190 | | Export Settings | 🟡 | - | - | - | - | Configure render settings |
191 | | Format Control | 🟡 | - | - | - | - | Control output format/codec |
192 | | Quick Export | 🟡 | - | - | - | - | RenderWithQuickExport |
193 | | Batch Rendering | 🟡 | - | - | - | - | Manage multiple render jobs |
194 | 
195 | ### Specialized Features
196 | 
197 | #### Object Inspection
198 | 
199 | | Feature | Implementation | Cursor (Mac) | Claude (Mac) | Cursor (Win) | Claude (Win) | Notes |
200 | |---------|---------------|--------------|--------------|--------------|--------------|-------|
201 | | Get Object Properties | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Get object properties - Not verified |
202 | | List Available Methods | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | List API methods for object - Not verified |
203 | | Get API Version | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Get DaVinci Resolve API version - Not verified |
204 | | Get Supported Objects | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | List supported API object types - Not verified |
205 | | Interactive Inspection | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Testing/debugging interface - Not verified |
206 | 
207 | #### Layout Presets
208 | 
209 | | Feature | Implementation | Cursor (Mac) | Claude (Mac) | Cursor (Win) | Claude (Win) | Notes |
210 | |---------|---------------|--------------|--------------|--------------|--------------|-------|
211 | | Get UI Layout Presets | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | List available layout presets - Not verified |
212 | | Set UI Layout Preset | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Switch to a specific UI layout - Not verified |
213 | | Save Current Layout | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Save current UI as layout preset - Not verified |
214 | | Delete Layout Preset | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Remove a custom layout preset - Not verified |
215 | 
216 | #### App Control
217 | 
218 | | Feature | Implementation | Cursor (Mac) | Claude (Mac) | Cursor (Win) | Claude (Win) | Notes |
219 | |---------|---------------|--------------|--------------|--------------|--------------|-------|
220 | | Quit Application | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Safely close DaVinci Resolve - Not verified (not testing to avoid closing app) |
221 | | Restart Application | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Restart DaVinci Resolve - Not verified (not testing to avoid disruption) |
222 | | Save All Projects | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Save all open projects - Not verified |
223 | | Check Application Status | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Verify if application is running - Not verified |
224 | 
225 | #### Cloud Project Operations
226 | 
227 | | Feature | Implementation | Cursor (Mac) | Claude (Mac) | Cursor (Win) | Claude (Win) | Notes |
228 | |---------|---------------|--------------|--------------|--------------|--------------|-------|
229 | | List Cloud Projects | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | List projects in cloud library - Not verified |
230 | | Create Cloud Project | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Create new project in cloud - Not verified |
231 | | Open Cloud Project | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Open project from cloud library - Not verified |
232 | | Delete Cloud Project | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Remove project from cloud - Not verified |
233 | | Export Project to Cloud | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Upload local project to cloud - Not verified |
234 | | Import Project from Cloud | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Download cloud project locally - Not verified |
235 | 
236 | #### Audio Sync Features
237 | 
238 | | Feature | Implementation | Cursor (Mac) | Claude (Mac) | Cursor (Win) | Claude (Win) | Notes |
239 | |---------|---------------|--------------|--------------|--------------|--------------|-------|
240 | | Auto-sync Audio | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Automatic audio synchronization - Not verified |
241 | | Waveform Analysis | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Sync based on waveform matching - Not verified |
242 | | Timecode Sync | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Sync based on embedded timecode - Not verified |
243 | | Multi-clip Sync | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Sync multiple clips simultaneously - Not verified |
244 | | Append Track Mode | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Option to append or replace audio - Not verified |
245 | | Manual Offset Adjustment | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Fine-tune sync with manual offset - Not verified |
246 | 
247 | #### Proxy Media Management
248 | 
249 | | Feature | Implementation | Cursor (Mac) | Claude (Mac) | Cursor (Win) | Claude (Win) | Notes |
250 | |---------|---------------|--------------|--------------|--------------|--------------|-------|
251 | | Link Proxy Media | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Link proxy files to clips - Not verified |
252 | | Unlink Proxy Media | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Remove proxy file associations - Not verified |
253 | | Set Proxy Mode | 🐞 | 🐞 | 🐞 | ⚠️ | ⚠️ | Toggle between proxy/original - Failed during testing |
254 | | Set Proxy Quality | 🐞 | 🐞 | 🐞 | ⚠️ | ⚠️ | Configure proxy resolution - Failed with "Failed to set proxy quality" |
255 | | Proxy Generation | 🟡 | - | - | - | - | Generate proxy media files |
256 | | Batch Proxy Operations | 🟡 | - | - | - | - | Process multiple clips at once |
257 | 
258 | #### Cache Management
259 | 
260 | | Feature | Implementation | Cursor (Mac) | Claude (Mac) | Cursor (Win) | Claude (Win) | Notes |
261 | |---------|---------------|--------------|--------------|--------------|--------------|-------|
262 | | Set Cache Mode | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Control cache utilization - Note: May require specific project setup |
263 | | Set Optimized Media Mode | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Toggle optimized media usage - Note: May require specific project setup |
264 | | Set Proxy Mode | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Toggle proxy mode - Note: May require specific project setup |
265 | | Set Proxy Quality | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Configure proxy quality |
266 | | Clear Cache | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Delete cached render files |
267 | | Cache Settings | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Configure cache parameters |
268 | | Generate Optimized Media | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Create optimized media |
269 | | Delete Optimized Media | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Remove optimized media files |
270 | 
271 | #### Transcription Services
272 | 
273 | | Feature | Implementation | Cursor (Mac) | Claude (Mac) | Cursor (Win) | Claude (Win) | Notes |
274 | |---------|---------------|--------------|--------------|--------------|--------------|-------|
275 | | Transcribe Audio | 🐞 | 🐞 | 🐞 | ⚠️ | ⚠️ | Generate text from audio - Failed with clip not found error |
276 | | Clear Transcription | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Remove existing transcription - Not verified |
277 | | Set Transcription Language | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Select language for transcription - Not verified |
278 | | Export Transcription | 🟡 | - | - | - | - | Save transcription to file |
279 | | Transcribe Multiple Clips | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Batch transcription processing - Not verified |
280 | | Edit Transcription | 🟡 | - | - | - | - | Modify generated text |
281 | 
282 | ## Object-Specific Methods
283 | 
284 | ### Timeline Object Methods
285 | 
286 | | Method | Implementation | Cursor (Mac) | Claude (Mac) | Cursor (Win) | Claude (Win) | Notes |
287 | |--------|---------------|--------------|--------------|--------------|--------------|-------|
288 | | GetName | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Get timeline name - Not verified |
289 | | GetStartFrame | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Get first frame number - Not verified |
290 | | GetEndFrame | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Get last frame number - Not verified |
291 | | GetTrackCount | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Count tracks by type - Not verified |
292 | | GetItemListInTrack | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Get clips in track - Not verified |
293 | | AddMarker | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Add marker at frame - Not verified |
294 | | GetMarkers | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Get all timeline markers - Not verified |
295 | | DeleteMarkerAtFrame | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Remove marker at position - Not verified |
296 | | DeleteMarkersByColor | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Remove markers by color - Not verified |
297 | | DeleteAllMarkers | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Clear all markers - Not verified |
298 | | ApplyGradeFromDRX | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Apply grade from file - Not verified |
299 | | GetSetting | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Get timeline setting - Not verified |
300 | | SetSetting | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Change timeline setting - Not verified |
301 | | InsertGeneratorIntoTimeline | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Add generator clip - Not verified |
302 | | InsertOFXGeneratorIntoTimeline | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Add OFX generator - Not verified |
303 | | InsertFusionGeneratorIntoTimeline | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Add Fusion generator - Not verified |
304 | | InsertTitleIntoTimeline | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Add title clip - Not verified |
305 | | InsertFusionTitleIntoTimeline | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Add Fusion title - Not verified |
306 | | InsertOFXTitleIntoTimeline | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Add OFX title - Not verified |
307 | | DuplicateTimeline | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Create timeline copy - Not verified |
308 | | CreateCompoundClip | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Group clips together - Not verified |
309 | | CreateFusionClip | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Convert to Fusion clip - Not verified |
310 | | ImportIntoTimeline | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Import timeline file - Not verified |
311 | | Export | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Export timeline file - Not verified |
312 | 
313 | ### TimelineItem Object Methods
314 | 
315 | | Method | Implementation | Cursor (Mac) | Claude (Mac) | Cursor (Win) | Claude (Win) | Notes |
316 | |--------|---------------|--------------|--------------|--------------|--------------|-------|
317 | | GetName | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Get clip name - Not verified |
318 | | GetDuration | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Get clip duration - Not verified |
319 | | GetStart | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Get start frame - Not verified |
320 | | GetEnd | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Get end frame - Not verified |
321 | | GetLeftOffset | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Get left handle length - Not verified |
322 | | GetRightOffset | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Get right handle length - Not verified |
323 | | GetProperty | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Get clip property - Not verified |
324 | | SetProperty | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Change clip property - Not verified |
325 | | AddMarker | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Add marker at offset - Not verified |
326 | | GetMarkers | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Get all clip markers - Not verified |
327 | | DeleteMarkerAtFrame | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Remove marker at position - Not verified |
328 | | DeleteMarkersByColor | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Remove markers by color - Not verified |
329 | | DeleteAllMarkers | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Clear all markers - Not verified |
330 | | AddFusionComp | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Add Fusion composition - Not verified |
331 | | ImportFusionComp | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Import Fusion composition - Not verified |
332 | | ExportFusionComp | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Export Fusion composition - Not verified |
333 | 
334 | ### Project Object Methods
335 | 
336 | | Method | Implementation | Cursor (Mac) | Claude (Mac) | Cursor (Win) | Claude (Win) | Notes |
337 | |--------|---------------|--------------|--------------|--------------|--------------|-------|
338 | | GetName | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Get project name - Not verified |
339 | | GetPresetList | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Get available presets - Not verified |
340 | | SetPreset | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Apply preset to project - Not verified |
341 | | AddRenderJob | 🐞 | 🐞 | 🐞 | ⚠️ | ⚠️ | Add job to render queue - Failed in our testing |
342 | | DeleteAllRenderJobs | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Clear render queue - Verified working |
343 | | StartRendering | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Begin render process - Not verified |
344 | | StopRendering | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Abort render process - Not verified |
345 | | IsRenderingInProgress | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Check render status - Not verified |
346 | | SetRenderFormat | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Set output format - Not verified |
347 | | LoadLayoutPreset | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Apply UI layout - Not verified |
348 | | SaveLayoutPreset | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Store current UI layout - Not verified |
349 | | ExportLayoutPreset | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Save layout to file - Not verified |
350 | | DeleteLayoutPreset | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Remove saved layout - Not verified |
351 | | GetSetting | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Get project setting - Not verified |
352 | | SetSetting | 🐞 | 🐞 | 🐞 | ⚠️ | ⚠️ | Change project setting - Failed with parameter type issues |
353 | | GetRenderJobStatus | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Get job progress info - Not verified |
354 | | GetRenderPresetList | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | List render presets - Not verified |
355 | | ImportRenderPresets | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Import presets file - Not verified |
356 | | ExportRenderPresets | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Export presets to file - Not verified |
357 | | GetCurrentRenderFormatAndCodec | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Get format settings - Not verified |
358 | | SetCurrentRenderFormatAndCodec | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Set format settings - Not verified |
359 | 
360 | ### MediaPool Object Methods
361 | 
362 | | Method | Implementation | Cursor (Mac) | Claude (Mac) | Cursor (Win) | Claude (Win) | Notes |
363 | |--------|---------------|--------------|--------------|--------------|--------------|-------|
364 | | GetRootFolder | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Get root media folder - Not verified |
365 | | AddSubFolder | 🐞 | 🐞 | 🐞 | ⚠️ | ⚠️ | Create new subfolder - Failed with existing folder name |
366 | | CreateEmptyTimeline | 🐞 | 🐞 | 🐞 | ⚠️ | ⚠️ | Create blank timeline - Failed with existing name |
367 | | AppendToTimeline | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Add clips to timeline - Not verified |
368 | | ImportMedia | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Import media files - Not verified |
369 | | ExportMetadata | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Export clip metadata - Not verified |
370 | | DeleteClips | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Remove clips from pool - Not verified |
371 | | MoveClips | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Move clips between bins - Not verified |
372 | | GetCurrentFolder | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Get active folder - Not verified |
373 | | SetCurrentFolder | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Switch active folder - Not verified |
374 | | GetClipMatteList | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Get clip matte files - Not verified |
375 | | AddClipMatte | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Add matte to clip - Not verified |
376 | | DeleteClipMatte | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Remove clip matte - Not verified |
377 | | RelinkClips | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Reconnect media files - Not verified |
378 | | UnlinkClips | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Disconnect media files - Not verified |
379 | | LinkProxyMedia | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Connect proxy media - Not verified |
380 | | UnlinkProxyMedia | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Remove proxy links - Not verified |
381 | | ReplaceClip | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Replace with new media - Not verified |
382 | 
383 | ### Gallery Object Methods
384 | 
385 | | Method | Implementation | Cursor (Mac) | Claude (Mac) | Cursor (Win) | Claude (Win) | Notes |
386 | |--------|---------------|--------------|--------------|--------------|--------------|-------|
387 | | GetAlbumName | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Get current album name - Not verified |
388 | | SetAlbumName | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Rename current album - Not verified |
389 | | GetCurrentAlbum | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Get active album - Not verified |
390 | | SetCurrentAlbum | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Switch to album - Not verified |
391 | | GetAlbumList | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | List all albums - Not verified |
392 | | CreateAlbum | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Create new album - Not verified |
393 | | DeleteAlbum | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Remove album - Not verified |
394 | | GetStillList | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | List album stills - Not verified |
395 | | DeleteStill | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Delete still - Not verified |
396 | | ExportStills | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Save stills to files - Not verified |
397 | | ImportStills | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Load stills from files - Not verified |
398 | 
399 | ### ColorPage Object Methods
400 | 
401 | | Method | Implementation | Cursor (Mac) | Claude (Mac) | Cursor (Win) | Claude (Win) | Notes |
402 | |--------|---------------|--------------|--------------|--------------|--------------|-------|
403 | | GetLUTs | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Get available LUTs - Not verified |
404 | | GetCurrentNode | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Get active color node - Not verified |
405 | | GetNodeList | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | List all color nodes - Not verified |
406 | | SelectNode | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Switch active node - Not verified |
407 | | AddNode | 🐞 | 🐞 | 🐞 | ⚠️ | ⚠️ | Add new node - Failed with "Cannot access grade object" |
408 | | DeleteNode | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Remove node - Not verified |
409 | | SetPrimaryColorGrade | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Apply primary correction - Not verified |
410 | | SetColorWheelPrimaryParam | 🐞 | 🐞 | 🐞 | ⚠️ | ⚠️ | Adjust primary wheel - Failed with "Cannot access grade object" |
411 | | SetColorWheelLogParam | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Adjust log wheel - Not verified |
412 | | GetKeyframeMode | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Get keyframe mode - Not verified |
413 | | SetKeyframeMode | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Set keyframe mode - Not verified |
414 | | ApplyLUT | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Apply LUT to node - Not verified |
415 | | ExportLUT | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Export node as LUT - Not verified |
416 | | GetColorVersion | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Get current version - Not verified |
417 | | GetColorVersions | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | List all versions - Not verified |
418 | | CreateColorVersion | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Create new version - Not verified |
419 | | DeleteColorVersion | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Remove version - Not verified |
420 | | LoadColorVersion | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Switch to version - Not verified |
421 | | GetColorGroupList | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | List color groups - Not verified |
422 | | CreateColorGroup | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Create new group - Not verified |
423 | | DeleteColorGroup | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Remove group - Not verified |
424 | 
425 | ### Delivery Object Methods
426 | 
427 | | Method | Implementation | Cursor (Mac) | Claude (Mac) | Cursor (Win) | Claude (Win) | Notes |
428 | |--------|---------------|--------------|--------------|--------------|--------------|-------|
429 | | AddRenderJob | 🐞 | 🐞 | 🐞 | ⚠️ | ⚠️ | Add to render queue - Failed in our testing |
430 | | DeleteRenderJob | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Remove render job - Not verified |
431 | | DeleteAllRenderJobs | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Clear render queue - Verified working |
432 | | GetRenderJobList | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | List queued jobs - Not verified |
433 | | GetRenderPresetList | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | List available presets - Not verified |
434 | | GetRenderFormats | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | List output formats - Not verified |
435 | | GetRenderCodecs | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | List available codecs - Not verified |
436 | | RenderJobStatus | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Get job status - Not verified |
437 | | IsRenderingInProgress | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Check render activity - Not verified |
438 | | StartRendering | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Begin render process - Not verified |
439 | | StopRendering | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Cancel rendering - Not verified |
440 | 
441 | ## Implementation Details
442 | 
443 | ### Object Inspection
444 | 
445 | The object inspection implementation provides comprehensive functionality for:
446 | 
447 | 1. **API Exploration** - Inspect Resolve API objects to discover methods and properties
448 | 2. **Method Analysis** - Get detailed information about object methods and their parameters
449 | 3. **Property Inspection** - Access object properties with type information
450 | 4. **Python Integration** - Combines Python's introspection with structured output
451 | 5. **Documentation Generation** - Can be used to create documentation for API objects
452 | 
453 | ### Layout Presets
454 | 
455 | The layout presets implementation enables:
456 | 
457 | 1. **Preset Management** - List, save, load, export, and import UI layout presets
458 | 2. **User Interface Customization** - Store and recall different UI layouts for different tasks
459 | 3. **Workflow Optimization** - Quick switching between different interface configurations
460 | 4. **Cross-Project Sharing** - Export and import layouts between different projects or systems
461 | 
462 | ### App Control
463 | 
464 | The app control implementation provides:
465 | 
466 | 1. **Application Management** - Functions to control the Resolve application itself
467 | 2. **State Monitoring** - Check application state and version information
468 | 3. **Settings Access** - Open project settings and preferences dialogs
469 | 4. **Session Control** - Safely quit or restart the application programmatically
470 | 
471 | ### Cloud Project Operations
472 | 
473 | The cloud project operations implementation provides:
474 | 
475 | 1. **Cloud Project Creation** - Create new cloud projects with specified settings
476 | 2. **Project Restoration** - Restore cloud projects from online storage
477 | 3. **Import Functionality** - Import cloud projects into the local database
478 | 4. **User Management** - Add, remove, and manage users for collaborative workflow
479 | 5. **Export Functions** - Export local projects to cloud storage
480 | 
481 | ### Audio Synchronization
482 | 
483 | The audio synchronization implementation supports:
484 | 
485 | 1. **Multi-camera workflows** - Synchronizing video clips from multiple cameras with separate audio
486 | 2. **External audio sources** - Integrating audio from external recorders
487 | 3. **Sync method options** - Support for both waveform analysis and timecode-based synchronization
488 | 4. **Organization workflow** - Automatic organization of synced clips into dedicated bins
489 | 
490 | The implementation supports these parameters:
491 | 
492 | 1. **clip_names** - List of clips to synchronize
493 | 2. **sync_method** - "waveform" (audio pattern matching) or "timecode" (TC matching)
494 | 3. **append_mode** - Toggle between appending audio tracks or replacing audio
495 | 4. **target_bin** - Optional bin name for organization
496 | 
497 | ### Proxy Media Management
498 | 
499 | Comprehensive proxy media functionality for:
500 | 
501 | 1. **Proxy workflow support** - Connecting high-resolution clips to lower-resolution proxy media
502 | 2. **Performance optimization** - Improving playback performance with proxy media
503 | 3. **Quality toggling** - Easily switching between proxy and full-resolution media
504 | 4. **Path management** - Maintaining proper file paths for proxies
505 | 5. **Quality settings** - Control proxy generation quality (quarter, half, three-quarter, full)
506 | 
507 | ### Cache Management  
508 | 
509 | The cache management implementation provides detailed control over:
510 | 
511 | 1. **Cache Modes** - Control over cache usage with Auto/On/Off settings  
512 | 2. **Optimized Media** - Management of optimized media settings and generation
513 | 3. **Proxy Media** - Control of proxy media settings, quality, and usage
514 | 4. **Mode Selection** - Simple mode selection with human-friendly options
515 | 
516 | ## Planned Features
517 | 
518 | Next development priorities:
519 | 
520 | 1. **Fusion Page Integration** - Access to Fusion scripting and composition management
521 | 2. **Fairlight Page Operations** - Audio editing and mixing functionality
522 | 3. **Media Storage Management** - Advanced media storage and organization tools
523 | 4. **Render Job Operations** - Comprehensive render queue management with job ID support
524 | 5. **Timeline Export Properties** - Export formats including AAF, XML, EDL, etc.
525 | 6. **Windows Platform Compatibility** - Ensuring full functionality across platforms
526 | 
527 | ### Fairlight Page Operations
528 | 
529 | | Feature | Implementation | Cursor (Mac) | Claude (Mac) | Cursor (Win) | Claude (Win) | Notes |
530 | |---------|---------------|--------------|--------------|--------------|--------------|-------|
531 | | Audio Levels | 🟡 | - | - | - | - | Control audio levels |
532 | | Audio Effects | 🟡 | - | - | - | - | Apply audio effects |
533 | | Audio Routing | 🟡 | - | - | - | - | Configure audio routing |
534 | | Audio Metering | 🟡 | - | - | - | - | Monitor audio levels |
535 | | Track Management | 🟡 | - | - | - | - | Add/remove/edit audio tracks |
536 | | Sound Libraries | 🟡 | - | - | - | - | Access sound effects libraries |
537 | | Voice Isolation | 🟡 | - | - | - | - | AI-powered voice separation |
538 | | Noise Removal | 🟡 | - | - | - | - | Audio cleanup tools |
539 | 
540 | ### Fusion Page Integration
541 | 
542 | | Feature | Implementation | Cursor (Mac) | Claude (Mac) | Cursor (Win) | Claude (Win) | Notes |
543 | |---------|---------------|--------------|--------------|--------------|--------------|-------|
544 | | Fusion Composition | 🟡 | - | - | - | - | Create/edit Fusion compositions |
545 | | Node Graph | 🟡 | - | - | - | - | Work with Fusion node graph |
546 | | Add Effects | 🟡 | - | - | - | - | Add visual effects nodes |
547 | | Animation | 🟡 | - | - | - | - | Animate properties and parameters |
548 | | Templates | 🟡 | - | - | - | - | Use/save effect templates |
549 | | 3D Objects | 🟡 | - | - | - | - | Work with 3D elements |
550 | | Particle Systems | 🟡 | - | - | - | - | Create and edit particle effects |
551 | | Text Generation | 🟡 | - | - | - | - | Create text effects and animations |
552 | 
553 | ### Edit Page Operations
554 | 
555 | | Feature | Implementation | Cursor (Mac) | Claude (Mac) | Cursor (Win) | Claude (Win) | Notes |
556 | |---------|---------------|--------------|--------------|--------------|--------------|-------|
557 | | Clip Editing | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Edit clip properties |
558 | | Transitions | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Add/edit transitions |
559 | | Effects | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Apply video effects |
560 | | Generators | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Add titles, solids, etc. |
561 | | Speed Effects | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | Control clip playback speed |
562 | | Dynamic Zoom | 🟡 | - | - | - | - | Ken Burns style effects |
563 | | Stabilization | 🟡 | - | - | - | - | Video stabilization tools |
564 | | Smart Reframe | 🟡 | - | - | - | - | AI-based reframing for different aspect ratios |
565 | 
566 | ## Testing Summary
567 | 
568 | During our testing process, we've identified several key issues and limitations:
569 | 
570 | 1. **Color Page Operations**: Several color-related operations failed with "Cannot access grade object" errors, including AddNode and SetColorWheelPrimaryParam. These issues may be related to the current project state or clip selection.
571 | 
572 | 2. **Delivery Operations**: Adding render jobs to the queue consistently failed in our tests, though clearing the render queue works correctly.
573 | 
574 | 3. **Media Pool Operations**: Some operations such as creating new bins and timelines failed when existing items with the same name were present, indicating a need for better error handling or checking.
575 | 
576 | 4. **Proxy and Transcription**: Proxy and transcription operations failed with various issues, including "Clip not found" errors, suggesting the need for better media management integration.
577 | 
578 | 5. **Project Settings**: Setting project settings failed with parameter type issues, suggesting a mismatch between the expected and provided parameter formats.
579 | 
580 | ### Next Steps
581 | 
582 | Based on our testing, we recommend:
583 | 
584 | 1. Implementing better error handling and validation in the API endpoints
585 | 2. Adding more robust logging for troubleshooting
586 | 3. Creating comprehensive test cases for each feature category
587 | 4. Focusing on fixing the most critical issues in color grading and rendering first
588 | 5. Adding better documentation for parameter types and expected formats
589 | 
590 | The MCP server has good fundamental implementation but requires significant testing and debugging to reach production readiness.
591 | 
```

--------------------------------------------------------------------------------
/src/api/color_operations.py:
--------------------------------------------------------------------------------

```python
  1 | #!/usr/bin/env python3
  2 | """
  3 | DaVinci Resolve Color Page Operations
  4 | """
  5 | 
  6 | import logging
  7 | from typing import Dict, Any, List, Optional, Tuple, Union
  8 | 
  9 | logger = logging.getLogger("davinci-resolve-mcp.color")
 10 | 
 11 | def get_current_node(resolve) -> Dict[str, Any]:
 12 |     """Get information about the current node in the color page.
 13 |     
 14 |     Args:
 15 |         resolve: The DaVinci Resolve instance
 16 |     
 17 |     Returns:
 18 |         Dictionary with current node information
 19 |     """
 20 |     if resolve is None:
 21 |         return {"error": "Not connected to DaVinci Resolve"}
 22 |     
 23 |     project_manager = resolve.GetProjectManager()
 24 |     if not project_manager:
 25 |         return {"error": "Failed to get Project Manager"}
 26 |     
 27 |     current_project = project_manager.GetCurrentProject()
 28 |     if not current_project:
 29 |         return {"error": "No project currently open"}
 30 |     
 31 |     # First, ensure we're on the color page
 32 |     current_page = resolve.GetCurrentPage()
 33 |     if current_page.lower() != "color":
 34 |         return {"error": f"Not on Color page. Current page is: {current_page}"}
 35 |     
 36 |     # Get the current timeline
 37 |     current_timeline = current_project.GetCurrentTimeline()
 38 |     if not current_timeline:
 39 |         return {"error": "No timeline currently active"}
 40 |     
 41 |     try:
 42 |         # Access color-specific functionality through the timeline
 43 |         # First get the current clip in the timeline
 44 |         current_clip = current_timeline.GetCurrentVideoItem()
 45 |         if not current_clip:
 46 |             return {"error": "No clip is currently selected in the timeline"}
 47 |         
 48 |         # Get the clip's grade
 49 |         current_grade = current_clip.GetCurrentGrade()
 50 |         if not current_grade:
 51 |             return {"error": "Failed to get current grade"}
 52 |         
 53 |         # Get the currently selected node
 54 |         current_node_index = current_grade.GetCurrentNode()
 55 |         if current_node_index < 1:
 56 |             return {"error": "No node is currently selected"}
 57 |         
 58 |         # Get node count
 59 |         node_count = current_grade.GetNodeCount()
 60 |         
 61 |         # Get information about the current node
 62 |         node_info = {
 63 |             "clip_name": current_clip.GetName(),
 64 |             "node_index": current_node_index,
 65 |             "node_count": node_count,
 66 |             "is_serial": current_grade.IsSerial(current_node_index),
 67 |             "is_parallel": current_grade.IsParallel(current_node_index),
 68 |             "is_layer": current_grade.IsLayer(current_node_index),
 69 |         }
 70 |         
 71 |         # Try to get node name
 72 |         try:
 73 |             node_name = current_grade.GetNodeName(current_node_index)
 74 |             node_info["name"] = node_name
 75 |         except:
 76 |             node_info["name"] = f"Node {current_node_index}"
 77 |         
 78 |         # Try to get additional node properties if available
 79 |         try:
 80 |             # Check for common node properties that might be available
 81 |             properties = {}
 82 |             
 83 |             # Check if node is enabled
 84 |             try:
 85 |                 properties["enabled"] = current_grade.IsNodeEnabled(current_node_index)
 86 |             except:
 87 |                 pass
 88 |             
 89 |             # Get node type if available
 90 |             try:
 91 |                 properties["type"] = current_grade.GetNodeType(current_node_index)
 92 |             except:
 93 |                 pass
 94 |             
 95 |             # Add properties if we found any
 96 |             if properties:
 97 |                 node_info["properties"] = properties
 98 |         except:
 99 |             pass
100 |         
101 |         return node_info
102 |         
103 |     except Exception as e:
104 |         return {"error": f"Error getting current node: {str(e)}"}
105 | 
106 | def apply_lut(resolve, lut_path: str, node_index: int = None) -> str:
107 |     """Apply a LUT to a node in the color page.
108 |     
109 |     Args:
110 |         resolve: The DaVinci Resolve instance
111 |         lut_path: Path to the LUT file to apply
112 |         node_index: Index of the node to apply the LUT to (uses current node if None)
113 |     
114 |     Returns:
115 |         String indicating success or failure with detailed error message
116 |     """
117 |     if resolve is None:
118 |         return "Error: Not connected to DaVinci Resolve"
119 |     
120 |     # Validate LUT path
121 |     if not lut_path:
122 |         return "Error: LUT path cannot be empty"
123 |     
124 |     import os
125 |     if not os.path.exists(lut_path):
126 |         return f"Error: LUT file '{lut_path}' does not exist"
127 |     
128 |     # Check file extension for supported LUT types
129 |     valid_extensions = ['.cube', '.3dl', '.lut', '.mga']
130 |     file_extension = os.path.splitext(lut_path)[1].lower()
131 |     if file_extension not in valid_extensions:
132 |         return f"Error: Unsupported LUT file format. Supported formats: {', '.join(valid_extensions)}"
133 |     
134 |     project_manager = resolve.GetProjectManager()
135 |     if not project_manager:
136 |         return "Error: Failed to get Project Manager"
137 |     
138 |     current_project = project_manager.GetCurrentProject()
139 |     if not current_project:
140 |         return "Error: No project currently open"
141 |     
142 |     # First, ensure we're on the color page
143 |     current_page = resolve.GetCurrentPage()
144 |     if current_page.lower() != "color":
145 |         # Try to switch to color page
146 |         result = resolve.OpenPage("color")
147 |         if not result:
148 |             return f"Error: Failed to switch to Color page. Current page is: {current_page}"
149 |     
150 |     # Get the current timeline
151 |     current_timeline = current_project.GetCurrentTimeline()
152 |     if not current_timeline:
153 |         return "Error: No timeline currently active"
154 |     
155 |     try:
156 |         # Get the current clip in the timeline
157 |         current_clip = current_timeline.GetCurrentVideoItem()
158 |         if not current_clip:
159 |             return "Error: No clip is currently selected in the timeline"
160 |         
161 |         # Get the clip's grade
162 |         current_grade = current_clip.GetCurrentGrade()
163 |         if not current_grade:
164 |             return "Error: Failed to get current grade"
165 |         
166 |         # Determine which node to apply the LUT to
167 |         target_node_index = node_index
168 |         if target_node_index is None:
169 |             # Use the currently selected node
170 |             target_node_index = current_grade.GetCurrentNode()
171 |             if target_node_index < 1:
172 |                 return "Error: No node is currently selected"
173 |         else:
174 |             # Validate the provided node index
175 |             node_count = current_grade.GetNodeCount()
176 |             if target_node_index < 1 or target_node_index > node_count:
177 |                 return f"Error: Invalid node index {target_node_index}. Valid range: 1-{node_count}"
178 |         
179 |         # Apply the LUT to the node
180 |         result = current_grade.ApplyLUT(target_node_index, lut_path)
181 |         
182 |         if result:
183 |             # Try to get the node name for a better message
184 |             try:
185 |                 node_name = current_grade.GetNodeName(target_node_index)
186 |                 return f"Successfully applied LUT '{os.path.basename(lut_path)}' to node '{node_name}' (index {target_node_index})"
187 |             except:
188 |                 return f"Successfully applied LUT '{os.path.basename(lut_path)}' to node {target_node_index}"
189 |         else:
190 |             return f"Failed to apply LUT to node {target_node_index}"
191 |         
192 |     except Exception as e:
193 |         return f"Error applying LUT: {str(e)}"
194 | 
195 | def add_node(resolve, node_type: str = "serial", label: str = None) -> str:
196 |     """Add a new node to the current grade in the color page.
197 |     
198 |     Args:
199 |         resolve: The DaVinci Resolve instance
200 |         node_type: Type of node to add. Options: 'serial', 'parallel', 'layer'
201 |         label: Optional label/name for the new node
202 |     
203 |     Returns:
204 |         String indicating success or failure with detailed error message
205 |     """
206 |     if resolve is None:
207 |         return "Error: Not connected to DaVinci Resolve"
208 |     
209 |     # Validate node type
210 |     valid_node_types = ['serial', 'parallel', 'layer']
211 |     if node_type.lower() not in valid_node_types:
212 |         return f"Error: Invalid node type. Must be one of: {', '.join(valid_node_types)}"
213 |     
214 |     logger.info(f"Adding {node_type} node with label: {label if label else 'None'}")
215 |     
216 |     project_manager = resolve.GetProjectManager()
217 |     if not project_manager:
218 |         logger.error("Failed to get Project Manager")
219 |         return "Error: Failed to get Project Manager"
220 |     
221 |     current_project = project_manager.GetCurrentProject()
222 |     if not current_project:
223 |         logger.error("No project currently open")
224 |         return "Error: No project currently open"
225 |     
226 |     # First, ensure we're on the color page
227 |     current_page = resolve.GetCurrentPage()
228 |     if current_page.lower() != "color":
229 |         # Try to switch to color page
230 |         logger.info(f"Currently on {current_page} page, switching to color page")
231 |         result = resolve.OpenPage("color")
232 |         if not result:
233 |             logger.error(f"Failed to switch to Color page. Current page is: {current_page}")
234 |             return f"Error: Failed to switch to Color page. Current page is: {current_page}"
235 |         logger.info("Successfully switched to color page")
236 |     
237 |     # Get the current timeline
238 |     current_timeline = current_project.GetCurrentTimeline()
239 |     if not current_timeline:
240 |         logger.error("No timeline currently active")
241 |         return "Error: No timeline currently active"
242 |     
243 |     try:
244 |         # Use the helper function to ensure a clip is selected
245 |         clip_selected, current_clip, message = ensure_clip_selected(resolve, current_timeline)
246 |         
247 |         if not clip_selected or not current_clip:
248 |             logger.error("No clip could be selected automatically")
249 |             return f"Error: {message}. Please select a clip manually in DaVinci Resolve."
250 |         
251 |         logger.info(f"Working with clip: {current_clip.GetName()}")
252 |         
253 |         # Get the clip's grade
254 |         # This is where the NoneType error typically occurs
255 |         logger.info("Attempting to get current grade")
256 |         
257 |         # First method: Direct approach
258 |         try:
259 |             current_grade = current_clip.GetCurrentGrade()
260 |             if current_grade:
261 |                 logger.info("Successfully got current grade using GetCurrentGrade()")
262 |             else:
263 |                 logger.warning("GetCurrentGrade() returned None")
264 |         except Exception as e:
265 |             logger.error(f"Error getting current grade via GetCurrentGrade(): {str(e)}")
266 |             current_grade = None
267 |         
268 |         # Alternative approach if the first method failed
269 |         if not current_grade:
270 |             logger.info("Attempting alternative methods to access grade functionality")
271 |             
272 |             # Try to select the clip first to ensure it's active
273 |             try:
274 |                 # Ensure clip is selected in the timeline
275 |                 logger.info("Trying to select the clip in timeline again")
276 |                 current_timeline.SetCurrentVideoItem(current_clip)
277 |                 logger.info(f"Selected clip {current_clip.GetName()} in timeline")
278 |                 
279 |                 # Try to get grade again after selection
280 |                 current_grade = current_clip.GetCurrentGrade()
281 |                 if current_grade:
282 |                     logger.info("Successfully got current grade after selection")
283 |             except Exception as e:
284 |                 logger.error(f"Error in alternative selection approach: {str(e)}")
285 |         
286 |         # Direct node creation if we still don't have a grade object
287 |         if not current_grade:
288 |             logger.warning("Could not get grade object, attempting direct node creation")
289 |             
290 |             try:
291 |                 # Try using the node graph directly through ColorPage
292 |                 color_page = project_manager.GetCurrentPage()
293 |                 if color_page and hasattr(color_page, "GetNodeGraph"):
294 |                     node_graph = color_page.GetNodeGraph()
295 |                     if node_graph:
296 |                         logger.info("Successfully got node graph through ColorPage")
297 |                         
298 |                         # Direct node creation using node graph
299 |                         if node_type.lower() == "serial":
300 |                             result = node_graph.AddSerialNode()
301 |                         elif node_type.lower() == "parallel":
302 |                             result = node_graph.AddParallelNode()
303 |                         elif node_type.lower() == "layer":
304 |                             result = node_graph.AddLayerNode()
305 |                         
306 |                         if result:
307 |                             return f"Successfully added {node_type} node using direct NodeGraph approach"
308 |                         else:
309 |                             logger.error(f"Failed to add {node_type} node using NodeGraph")
310 |             except Exception as e:
311 |                 logger.error(f"Error in direct node creation attempt: {str(e)}")
312 |             
313 |             return f"Error adding {node_type} node: Cannot access grade object. The clip may not be properly graded yet."
314 |         
315 |         logger.info("Proceeding with node addition with valid grade object")
316 |         # Add the appropriate type of node
317 |         result = False
318 |         method_name = ""
319 |         
320 |         if node_type.lower() == "serial":
321 |             # Add a serial node after the current node
322 |             method_name = "AddSerialNode"
323 |             logger.info(f"Calling {method_name}()")
324 |             result = current_grade.AddSerialNode()
325 |         elif node_type.lower() == "parallel":
326 |             # Add a parallel node
327 |             method_name = "AddParallelNode"
328 |             logger.info(f"Calling {method_name}()")
329 |             result = current_grade.AddParallelNode()
330 |         elif node_type.lower() == "layer":
331 |             # Add a layer node
332 |             method_name = "AddLayerNode"
333 |             logger.info(f"Calling {method_name}()")
334 |             result = current_grade.AddLayerNode()
335 |         
336 |         if not result:
337 |             logger.error(f"Failed to add {node_type} node using {method_name}()")
338 |             return f"Failed to add {node_type} node using {method_name}()"
339 |         
340 |         # Get the new node count and find the newly added node
341 |         new_node_count = current_grade.GetNodeCount()
342 |         logger.info(f"New node count: {new_node_count}")
343 |         
344 |         # Get the new node index - it should be the currently selected node
345 |         new_node_index = current_grade.GetCurrentNode()
346 |         logger.info(f"New node index: {new_node_index}")
347 |         
348 |         # Set label if provided
349 |         if label and new_node_index > 0:
350 |             try:
351 |                 logger.info(f"Setting node label to '{label}'")
352 |                 current_grade.SetNodeLabel(new_node_index, label)
353 |                 node_label_info = f" with label '{label}'"
354 |             except Exception as e:
355 |                 logger.warning(f"Failed to set node label: {str(e)}")
356 |                 node_label_info = f" (couldn't set label to '{label}')"
357 |         else:
358 |             node_label_info = ""
359 |         
360 |         logger.info(f"Successfully added {node_type} node (index {new_node_index}){node_label_info}")
361 |         return f"Successfully added {node_type} node (index {new_node_index}){node_label_info}"
362 |         
363 |     except Exception as e:
364 |         logger.error(f"Error adding {node_type} node: {str(e)}")
365 |         return f"Error adding {node_type} node: {str(e)}"
366 | 
367 | def copy_grade(resolve, source_clip_name: str = None, target_clip_name: str = None, mode: str = "full") -> str:
368 |     """Copy a grade from one clip to another in the color page.
369 |     
370 |     Args:
371 |         resolve: The DaVinci Resolve instance
372 |         source_clip_name: Name of the source clip to copy grade from (uses current clip if None)
373 |         target_clip_name: Name of the target clip to apply grade to (uses current clip if None)
374 |         mode: What to copy - 'full' (entire grade), 'current_node', or 'all_nodes'
375 |     
376 |     Returns:
377 |         String indicating success or failure with detailed error message
378 |     """
379 |     if resolve is None:
380 |         return "Error: Not connected to DaVinci Resolve"
381 |     
382 |     # Validate copy mode
383 |     valid_modes = ['full', 'current_node', 'all_nodes']
384 |     if mode.lower() not in valid_modes:
385 |         return f"Error: Invalid copy mode. Must be one of: {', '.join(valid_modes)}"
386 |     
387 |     project_manager = resolve.GetProjectManager()
388 |     if not project_manager:
389 |         return "Error: Failed to get Project Manager"
390 |     
391 |     current_project = project_manager.GetCurrentProject()
392 |     if not current_project:
393 |         return "Error: No project currently open"
394 |     
395 |     # First, ensure we're on the color page
396 |     current_page = resolve.GetCurrentPage()
397 |     if current_page.lower() != "color":
398 |         # Try to switch to color page
399 |         result = resolve.OpenPage("color")
400 |         if not result:
401 |             return f"Error: Failed to switch to Color page. Current page is: {current_page}"
402 |     
403 |     # Get the current timeline
404 |     current_timeline = current_project.GetCurrentTimeline()
405 |     if not current_timeline:
406 |         return "Error: No timeline currently active"
407 |     
408 |     try:
409 |         # Get all clips in the timeline
410 |         all_video_clips = []
411 |         
412 |         # Get video track count
413 |         video_track_count = current_timeline.GetTrackCount("video")
414 |         
415 |         # Gather all clips from video tracks
416 |         for track_index in range(1, video_track_count + 1):
417 |             track_items = current_timeline.GetItemListInTrack("video", track_index)
418 |             if track_items:
419 |                 all_video_clips.extend(track_items)
420 |         
421 |         # Get the source clip
422 |         source_clip = None
423 |         if source_clip_name:
424 |             # Find the source clip by name
425 |             for clip in all_video_clips:
426 |                 if clip and clip.GetName() == source_clip_name:
427 |                     source_clip = clip
428 |                     break
429 |             
430 |             if not source_clip:
431 |                 return f"Error: Source clip '{source_clip_name}' not found in timeline"
432 |         else:
433 |             # Use the current clip as source
434 |             source_clip = current_timeline.GetCurrentVideoItem()
435 |             if not source_clip:
436 |                 return "Error: No clip is currently selected to use as source"
437 |             source_clip_name = source_clip.GetName()
438 |         
439 |         # Get the source grade
440 |         source_grade = source_clip.GetCurrentGrade()
441 |         if not source_grade:
442 |             return f"Error: Failed to get grade from source clip '{source_clip_name}'"
443 |         
444 |         # Get the target clip
445 |         target_clip = None
446 |         if target_clip_name:
447 |             # Check if target is same as source
448 |             if target_clip_name == source_clip_name:
449 |                 return f"Error: Source and target clips cannot be the same (both are '{source_clip_name}')"
450 |             
451 |             # Find the target clip by name
452 |             for clip in all_video_clips:
453 |                 if clip and clip.GetName() == target_clip_name:
454 |                     target_clip = clip
455 |                     break
456 |             
457 |             if not target_clip:
458 |                 return f"Error: Target clip '{target_clip_name}' not found in timeline"
459 |         else:
460 |             # Use the current clip as target (need to select a different clip first)
461 |             current_clip = current_timeline.GetCurrentVideoItem()
462 |             
463 |             if not current_clip:
464 |                 return "Error: No clip is currently selected to use as target"
465 |             
466 |             if current_clip.GetName() == source_clip_name:
467 |                 return "Error: Cannot copy grade to the same clip. Please specify a different target clip."
468 |             
469 |             target_clip = current_clip
470 |             target_clip_name = target_clip.GetName()
471 |         
472 |         # Get the target grade
473 |         target_grade = target_clip.GetCurrentGrade()
474 |         if not target_grade:
475 |             return f"Error: Failed to get grade from target clip '{target_clip_name}'"
476 |         
477 |         # Select the target clip to make it active for grade operations
478 |         current_timeline.SetCurrentVideoItem(target_clip)
479 |         
480 |         # Execute the copy based on the specified mode
481 |         result = False
482 |         if mode.lower() == "full":
483 |             # Copy the entire grade including all nodes
484 |             result = target_clip.CopyGrade(source_clip)
485 |         elif mode.lower() == "current_node":
486 |             # Copy only the current node from source to target
487 |             source_node_index = source_grade.GetCurrentNode()
488 |             target_node_index = target_grade.GetCurrentNode()
489 |             
490 |             if source_node_index < 1:
491 |                 return "Error: No node selected in source clip"
492 |             
493 |             if target_node_index < 1:
494 |                 return "Error: No node selected in target clip"
495 |             
496 |             # Copy the current node
497 |             result = target_grade.CopyFromNodeToNode(source_grade, source_node_index, target_node_index)
498 |         elif mode.lower() == "all_nodes":
499 |             # Copy all nodes but keep other grade settings
500 |             source_node_count = source_grade.GetNodeCount()
501 |             
502 |             if source_node_count < 1:
503 |                 return "Error: Source clip has no nodes to copy"
504 |             
505 |             # First, clear all nodes in target
506 |             target_node_count = target_grade.GetNodeCount()
507 |             for i in range(target_node_count, 0, -1):
508 |                 target_grade.DeleteNode(i)
509 |             
510 |             # Then, add nodes matching the source structure
511 |             for i in range(1, source_node_count + 1):
512 |                 # Determine node type
513 |                 if source_grade.IsSerial(i):
514 |                     target_grade.AddSerialNode()
515 |                 elif source_grade.IsParallel(i):
516 |                     target_grade.AddParallelNode()
517 |                 elif source_grade.IsLayer(i):
518 |                     target_grade.AddLayerNode()
519 |                 
520 |                 # Copy node settings
521 |                 new_node_index = target_grade.GetCurrentNode()
522 |                 if new_node_index > 0:
523 |                     target_grade.CopyFromNodeToNode(source_grade, i, new_node_index)
524 |             
525 |             result = True
526 |         
527 |         if result:
528 |             return f"Successfully copied grade from '{source_clip_name}' to '{target_clip_name}' using mode '{mode}'"
529 |         else:
530 |             return f"Failed to copy grade from '{source_clip_name}' to '{target_clip_name}' using mode '{mode}'"
531 |         
532 |     except Exception as e:
533 |         return f"Error copying grade: {str(e)}"
534 | 
535 | def get_color_wheels(resolve, node_index: int = None) -> Dict[str, Any]:
536 |     """Get color wheel parameters for a specific node.
537 |     
538 |     Args:
539 |         resolve: The DaVinci Resolve instance
540 |         node_index: Index of the node to get color wheels from (uses current node if None)
541 |     
542 |     Returns:
543 |         Dictionary with color wheel parameters
544 |     """
545 |     if resolve is None:
546 |         return {"error": "Not connected to DaVinci Resolve"}
547 |     
548 |     project_manager = resolve.GetProjectManager()
549 |     if not project_manager:
550 |         return {"error": "Failed to get Project Manager"}
551 |     
552 |     current_project = project_manager.GetCurrentProject()
553 |     if not current_project:
554 |         return {"error": "No project currently open"}
555 |     
556 |     # First, ensure we're on the color page
557 |     current_page = resolve.GetCurrentPage()
558 |     if current_page.lower() != "color":
559 |         return {"error": f"Not on Color page. Current page is: {current_page}"}
560 |     
561 |     # Get the current timeline
562 |     current_timeline = current_project.GetCurrentTimeline()
563 |     if not current_timeline:
564 |         return {"error": "No timeline currently active"}
565 |     
566 |     try:
567 |         # Get the current clip in the timeline
568 |         current_clip = current_timeline.GetCurrentVideoItem()
569 |         if not current_clip:
570 |             return {"error": "No clip is currently selected in the timeline"}
571 |         
572 |         # Get the clip's grade
573 |         current_grade = current_clip.GetCurrentGrade()
574 |         if not current_grade:
575 |             return {"error": "Failed to get current grade"}
576 |         
577 |         # Determine which node to get color wheels from
578 |         target_node_index = node_index
579 |         if target_node_index is None:
580 |             # Use the currently selected node
581 |             target_node_index = current_grade.GetCurrentNode()
582 |             if target_node_index < 1:
583 |                 return {"error": "No node is currently selected"}
584 |         else:
585 |             # Validate the provided node index
586 |             node_count = current_grade.GetNodeCount()
587 |             if target_node_index < 1 or target_node_index > node_count:
588 |                 return {"error": f"Invalid node index {target_node_index}. Valid range: 1-{node_count}"}
589 |         
590 |         # Get node name if available
591 |         node_name = ""
592 |         try:
593 |             node_name = current_grade.GetNodeName(target_node_index)
594 |         except:
595 |             node_name = f"Node {target_node_index}"
596 |         
597 |         # Get color wheel parameters
598 |         color_wheels = {
599 |             "node_index": target_node_index,
600 |             "node_name": node_name,
601 |             "clip_name": current_clip.GetName(),
602 |             "wheels": {}
603 |         }
604 |         
605 |         # Try to get each of the color wheels
606 |         wheels_to_get = [
607 |             {"name": "lift", "function_prefix": "GetLift"},
608 |             {"name": "gamma", "function_prefix": "GetGamma"},
609 |             {"name": "gain", "function_prefix": "GetGain"},
610 |             {"name": "offset", "function_prefix": "GetOffset"},
611 |         ]
612 |         
613 |         for wheel in wheels_to_get:
614 |             wheel_name = wheel["name"]
615 |             prefix = wheel["function_prefix"]
616 |             
617 |             wheel_data = {}
618 |             try:
619 |                 # Try to get R, G, B, and Y (master) values
620 |                 for channel, channel_name in [("R", "red"), ("G", "green"), ("B", "blue"), ("Y", "master")]:
621 |                     # Build the function name dynamically
622 |                     function_name = f"{prefix}{channel}"
623 |                     
624 |                     if hasattr(current_grade, function_name):
625 |                         # Call the function with the node index
626 |                         getter_func = getattr(current_grade, function_name)
627 |                         value = getter_func(target_node_index)
628 |                         wheel_data[channel_name] = value
629 |                 
630 |                 if wheel_data:
631 |                     color_wheels["wheels"][wheel_name] = wheel_data
632 |             except Exception as e:
633 |                 color_wheels["wheels"][wheel_name] = {"error": f"Could not get {wheel_name} wheel: {str(e)}"}
634 |         
635 |         # Try to get additional common color controls
636 |         try:
637 |             additional_controls = {}
638 |             
639 |             # Try to get contrast
640 |             try:
641 |                 if hasattr(current_grade, "GetContrast"):
642 |                     additional_controls["contrast"] = current_grade.GetContrast(target_node_index)
643 |             except:
644 |                 pass
645 |             
646 |             # Try to get saturation
647 |             try:
648 |                 if hasattr(current_grade, "GetSaturation"):
649 |                     additional_controls["saturation"] = current_grade.GetSaturation(target_node_index)
650 |             except:
651 |                 pass
652 |             
653 |             # Try to get color temperature
654 |             try:
655 |                 if hasattr(current_grade, "GetColorTemp"):
656 |                     additional_controls["color_temp"] = current_grade.GetColorTemp(target_node_index)
657 |             except:
658 |                 pass
659 |             
660 |             # Try to get tint
661 |             try:
662 |                 if hasattr(current_grade, "GetTint"):
663 |                     additional_controls["tint"] = current_grade.GetTint(target_node_index)
664 |             except:
665 |                 pass
666 |             
667 |             # Add additional controls if any were found
668 |             if additional_controls:
669 |                 color_wheels["additional_controls"] = additional_controls
670 |         except:
671 |             pass
672 |         
673 |         return color_wheels
674 |         
675 |     except Exception as e:
676 |         return {"error": f"Error getting color wheel parameters: {str(e)}"}
677 | 
678 | def set_color_wheel_param(resolve, wheel: str, param: str, value: float, node_index: int = None) -> str:
679 |     """Set a color wheel parameter for a node.
680 |     
681 |     Args:
682 |         resolve: The DaVinci Resolve instance
683 |         wheel: Which color wheel to adjust ('lift', 'gamma', 'gain', 'offset')
684 |         param: Which parameter to adjust ('red', 'green', 'blue', 'master')
685 |         value: The value to set (typically between -1.0 and 1.0)
686 |         node_index: Index of the node to set parameter for (uses current node if None)
687 |     
688 |     Returns:
689 |         String indicating success or failure with detailed error message
690 |     """
691 |     if resolve is None:
692 |         return "Error: Not connected to DaVinci Resolve"
693 |     
694 |     # Validate wheel
695 |     valid_wheels = ['lift', 'gamma', 'gain', 'offset']
696 |     if wheel.lower() not in valid_wheels:
697 |         return f"Error: Invalid wheel name. Must be one of: {', '.join(valid_wheels)}"
698 |     
699 |     # Validate parameter
700 |     valid_params = ['red', 'green', 'blue', 'master']
701 |     if param.lower() not in valid_params:
702 |         return f"Error: Invalid parameter name. Must be one of: {', '.join(valid_params)}"
703 |     
704 |     logger.info(f"Setting {wheel} {param} to {value}")
705 |     
706 |     # Map parameter names to channel identifiers used in the API
707 |     param_to_channel = {
708 |         'red': 'R',
709 |         'green': 'G',
710 |         'blue': 'B',
711 |         'master': 'Y'
712 |     }
713 |     
714 |     # Map wheel names to function name prefixes used in the API
715 |     wheel_to_function_prefix = {
716 |         'lift': 'SetLift',
717 |         'gamma': 'SetGamma',
718 |         'gain': 'SetGain',
719 |         'offset': 'SetOffset'
720 |     }
721 |     
722 |     project_manager = resolve.GetProjectManager()
723 |     if not project_manager:
724 |         logger.error("Failed to get Project Manager")
725 |         return "Error: Failed to get Project Manager"
726 |     
727 |     current_project = project_manager.GetCurrentProject()
728 |     if not current_project:
729 |         logger.error("No project currently open")
730 |         return "Error: No project currently open"
731 |     
732 |     # First, ensure we're on the color page
733 |     current_page = resolve.GetCurrentPage()
734 |     if current_page.lower() != "color":
735 |         # Try to switch to color page
736 |         logger.info(f"Currently on {current_page} page, switching to color page")
737 |         result = resolve.OpenPage("color")
738 |         if not result:
739 |             logger.error(f"Failed to switch to Color page. Current page is: {current_page}")
740 |             return f"Error: Failed to switch to Color page. Current page is: {current_page}"
741 |         logger.info("Successfully switched to color page")
742 |     
743 |     # Get the current timeline
744 |     current_timeline = current_project.GetCurrentTimeline()
745 |     if not current_timeline:
746 |         logger.error("No timeline currently active")
747 |         return "Error: No timeline currently active"
748 |     
749 |     try:
750 |         # Use the helper function to ensure a clip is selected
751 |         clip_selected, current_clip, message = ensure_clip_selected(resolve, current_timeline)
752 |         
753 |         if not clip_selected or not current_clip:
754 |             logger.error("No clip could be selected automatically")
755 |             return f"Error: {message}. Please select a clip manually in DaVinci Resolve."
756 |         
757 |         logger.info(f"Working with clip: {current_clip.GetName()}")
758 |         
759 |         # Get the clip's grade
760 |         # This is where the NoneType error typically occurs
761 |         logger.info("Attempting to get current grade")
762 |         
763 |         # First method: Direct approach
764 |         try:
765 |             current_grade = current_clip.GetCurrentGrade()
766 |             if current_grade:
767 |                 logger.info("Successfully got current grade using GetCurrentGrade()")
768 |             else:
769 |                 logger.warning("GetCurrentGrade() returned None")
770 |         except Exception as e:
771 |             logger.error(f"Error getting current grade via GetCurrentGrade(): {str(e)}")
772 |             current_grade = None
773 |         
774 |         # Alternative approach if the first method failed
775 |         if not current_grade:
776 |             logger.info("Attempting alternative methods to access grade functionality")
777 |             
778 |             # Try to select the clip first to ensure it's active
779 |             try:
780 |                 # Ensure clip is selected in the timeline
781 |                 logger.info("Trying to select the clip in timeline again")
782 |                 current_timeline.SetCurrentVideoItem(current_clip)
783 |                 logger.info(f"Selected clip {current_clip.GetName()} in timeline")
784 |                 
785 |                 # Try to get grade again after selection
786 |                 current_grade = current_clip.GetCurrentGrade()
787 |                 if current_grade:
788 |                     logger.info("Successfully got current grade after selection")
789 |             except Exception as e:
790 |                 logger.error(f"Error in alternative selection approach: {str(e)}")
791 |         
792 |         # Check if we have a valid grade object
793 |         if not current_grade:
794 |             logger.error("Could not get grade object after multiple attempts")
795 |             return "Error setting color wheel parameter: Cannot access grade object. The clip may not be properly graded yet."
796 |         
797 |         logger.info("Proceeding with parameter setting with valid grade object")
798 |         
799 |         # Determine which node to set parameter for
800 |         target_node_index = node_index
801 |         if target_node_index is None:
802 |             # Use the currently selected node
803 |             logger.info("Getting current node index")
804 |             target_node_index = current_grade.GetCurrentNode()
805 |             if target_node_index < 1:
806 |                 logger.error("No node is currently selected")
807 |                 return "Error: No node is currently selected"
808 |             logger.info(f"Using current node: {target_node_index}")
809 |         else:
810 |             # Validate the provided node index
811 |             logger.info(f"Validating provided node index: {target_node_index}")
812 |             node_count = current_grade.GetNodeCount()
813 |             if target_node_index < 1 or target_node_index > node_count:
814 |                 logger.error(f"Invalid node index {target_node_index}. Valid range: 1-{node_count}")
815 |                 return f"Error: Invalid node index {target_node_index}. Valid range: 1-{node_count}"
816 |         
817 |         # Get node name for better reporting
818 |         node_name = ""
819 |         try:
820 |             logger.info(f"Getting name for node {target_node_index}")
821 |             node_name = current_grade.GetNodeName(target_node_index) or f"Node {target_node_index}"
822 |             logger.info(f"Node name: {node_name}")
823 |         except Exception as e:
824 |             logger.warning(f"Could not get node name: {str(e)}")
825 |             node_name = f"Node {target_node_index}"
826 |         
827 |         # Build the function name to call
828 |         channel = param_to_channel[param.lower()]
829 |         function_prefix = wheel_to_function_prefix[wheel.lower()]
830 |         function_name = f"{function_prefix}{channel}"
831 |         logger.info(f"Function to call: {function_name}")
832 |         
833 |         # Check if the function exists
834 |         if not hasattr(current_grade, function_name):
835 |             logger.error(f"Function '{function_name}' not found in DaVinci Resolve API")
836 |             return f"Error: Function '{function_name}' not found in DaVinci Resolve API for setting {wheel} {param}"
837 |         
838 |         # Get the setter function
839 |         setter_func = getattr(current_grade, function_name)
840 |         
841 |         # Set the parameter value
842 |         logger.info(f"Calling {function_name}({target_node_index}, {value})")
843 |         result = setter_func(target_node_index, value)
844 |         
845 |         if result:
846 |             logger.info(f"Successfully set {wheel} {param} to {value} for {node_name}")
847 |             return f"Successfully set {wheel} {param} to {value} for {node_name}"
848 |         else:
849 |             logger.error(f"Failed to set {wheel} {param} to {value} for {node_name}")
850 |             return f"Failed to set {wheel} {param} to {value} for {node_name}"
851 |         
852 |     except Exception as e:
853 |         logger.error(f"Error setting color wheel parameter: {str(e)}")
854 |         return f"Error setting color wheel parameter: {str(e)}"
855 | 
856 | def ensure_clip_selected(resolve, timeline) -> Tuple[bool, Optional[Any], str]:
857 |     """Ensures a clip is selected in the timeline, selecting the first clip if needed.
858 |     
859 |     Args:
860 |         resolve: The DaVinci Resolve instance
861 |         timeline: The current timeline
862 |         
863 |     Returns:
864 |         Tuple containing (success, clip_object, message)
865 |     """
866 |     # First check if there's already a clip selected
867 |     current_clip = timeline.GetCurrentVideoItem()
868 |     if current_clip:
869 |         logger.info(f"Clip already selected: {current_clip.GetName()}")
870 |         return True, current_clip, f"Using currently selected clip: {current_clip.GetName()}"
871 |     
872 |     # No clip selected, try to select the first clip
873 |     logger.info("No clip currently selected, attempting to select first clip")
874 |     try:
875 |         # Get video tracks
876 |         video_track_count = timeline.GetTrackCount("video")
877 |         logger.info(f"Timeline has {video_track_count} video tracks")
878 |         
879 |         # Check each track for clips
880 |         for track_index in range(1, video_track_count + 1):
881 |             logger.info(f"Checking video track {track_index}")
882 |             
883 |             # Get clips in this track
884 |             track_items = timeline.GetItemListInTrack("video", track_index)
885 |             if not track_items or len(track_items) == 0:
886 |                 logger.info(f"No clips in track {track_index}")
887 |                 continue
888 |                 
889 |             logger.info(f"Found {len(track_items)} clips in track {track_index}")
890 |             
891 |             # Try to select the first clip
892 |             first_clip = track_items[0]
893 |             if first_clip:
894 |                 clip_name = first_clip.GetName()
895 |                 logger.info(f"Attempting to select clip: {clip_name}")
896 |                 
897 |                 # Set it as the current clip
898 |                 timeline.SetCurrentVideoItem(first_clip)
899 |                 
900 |                 # Verify selection
901 |                 selected_clip = timeline.GetCurrentVideoItem()
902 |                 if selected_clip and selected_clip.GetName() == clip_name:
903 |                     logger.info(f"Successfully selected first clip: {clip_name}")
904 |                     return True, selected_clip, f"Automatically selected clip: {clip_name}"
905 |                 else:
906 |                     logger.warning("Failed to verify clip selection")
907 |             
908 |             # If we got here, we couldn't select a clip in this track
909 |             logger.warning(f"Could not select a clip in track {track_index}")
910 |         
911 |         # If we reach here, we couldn't find or select any clips
912 |         logger.warning("No clips found in any video track, or could not select any")
913 |         return False, None, "Could not find any clips in the timeline to select"
914 |         
915 |     except Exception as e:
916 |         logger.error(f"Error attempting to select a clip: {str(e)}")
917 |         return False, None, f"Error selecting clip: {str(e)}" 
```
Page 4/5FirstPrevNextLast