resolve-ext 1.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- resolve_ext-1.0.1/LICENSE +21 -0
- resolve_ext-1.0.1/PKG-INFO +505 -0
- resolve_ext-1.0.1/README.md +475 -0
- resolve_ext-1.0.1/pyproject.toml +44 -0
- resolve_ext-1.0.1/resolve_ext.egg-info/PKG-INFO +505 -0
- resolve_ext-1.0.1/resolve_ext.egg-info/SOURCES.txt +15 -0
- resolve_ext-1.0.1/resolve_ext.egg-info/dependency_links.txt +1 -0
- resolve_ext-1.0.1/resolve_ext.egg-info/entry_points.txt +2 -0
- resolve_ext-1.0.1/resolve_ext.egg-info/requires.txt +4 -0
- resolve_ext-1.0.1/resolve_ext.egg-info/top_level.txt +1 -0
- resolve_ext-1.0.1/server.py +2881 -0
- resolve_ext-1.0.1/setup.cfg +4 -0
- resolve_ext-1.0.1/tests/test_camera.py +201 -0
- resolve_ext-1.0.1/tests/test_e2e.py +277 -0
- resolve_ext-1.0.1/tests/test_extras.py +115 -0
- resolve_ext-1.0.1/tests/test_offline.py +172 -0
- resolve_ext-1.0.1/tests/test_phase1.py +210 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 resolve-ext contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: resolve-ext
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: MCP server adding the DaVinci Resolve scripting surfaces mainstream MCP servers do not cover
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/CaptRauti/ResolveMCP-ext
|
|
7
|
+
Project-URL: Repository, https://github.com/CaptRauti/ResolveMCP-ext
|
|
8
|
+
Project-URL: Issues, https://github.com/CaptRauti/ResolveMCP-ext/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/CaptRauti/ResolveMCP-ext/releases
|
|
10
|
+
Keywords: davinci-resolve,mcp,model-context-protocol,video-editing,blackmagic
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
14
|
+
Classifier: Operating System :: MacOS
|
|
15
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
16
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Multimedia :: Video
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: mcp[cli]<2,>=1.30
|
|
27
|
+
Provides-Extra: test
|
|
28
|
+
Requires-Dist: pytest>=7; extra == "test"
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# resolve-ext
|
|
32
|
+
|
|
33
|
+
<!-- mcp-name: io.github.CaptRauti/resolve-ext -->
|
|
34
|
+
|
|
35
|
+
An MCP server that adds the **DaVinci Resolve 21.1.2 scripting surfaces the
|
|
36
|
+
mainstream Resolve MCP servers do not cover** — clip effects, effect
|
|
37
|
+
properties, Paste-Attributes, frame export and media-pool folder lookups.
|
|
38
|
+
|
|
39
|
+
It is deliberately an **extension, not a replacement**: register it alongside
|
|
40
|
+
the other two Resolve MCP servers and you get a larger surface with no overlap —
|
|
41
|
+
see [the full setup](#the-full-resolve-mcp-setup--three-servers-all-of-them).
|
|
42
|
+
Everything it exposes is measured against a live Resolve, and calls that turn
|
|
43
|
+
out to be broken are reported as broken rather than wrapped in a success.
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
effects apply/list/remove/toggle/reorder clip effects
|
|
47
|
+
clip_attrs copy attributes between clips (Paste Attributes)
|
|
48
|
+
export_frame stills from a clip or media-pool item
|
|
49
|
+
folder_ops which bin does this clip/folder live in
|
|
50
|
+
photo_albums albums, photos, ratings, flags, versions, stills
|
|
51
|
+
node_graph node labels, colours, enable state, LUTs, cache
|
|
52
|
+
project_extras render presets, audio render formats, transcription
|
|
53
|
+
camera_comparison 2x2 / split layouts, camera labels, markers
|
|
54
|
+
clip_position read placement + handle offsets; flags broken APIs
|
|
55
|
+
resolve_ext_status health check
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## The full Resolve MCP setup — three servers, all of them
|
|
59
|
+
|
|
60
|
+
Resolve is not covered by one MCP server. **Three independent servers do
|
|
61
|
+
different jobs, and full coverage means running all three.** This project is the
|
|
62
|
+
third; the other two are not optional extras.
|
|
63
|
+
|
|
64
|
+
| Server | Origin | Tools | What it is for |
|
|
65
|
+
|---|---|---|---|
|
|
66
|
+
| **Blackmagic's own**<br>`ResolveMCP.exe` | **Ships with DaVinci Resolve 21** — nothing to download | 14 | A *meta-server*: it provides Resolve's scripting-API stubs and a Python sandbox (`run_script`, `search_scripting_api`, `get_whats_new`) plus LUT/DCTL management. It performs no Resolve actions itself. |
|
|
67
|
+
| **community**<br>[`samuelgursky/davinci-resolve-mcp`](https://github.com/samuelgursky/davinci-resolve-mcp) | `npx` or git | 37<br>(390 with `--full`) | The workhorse: editing, media pool, render, markers, grading, Fusion — one granular tool per task. Ships a second `davinci-resolve-advanced` server (18 Node tools) for source-safe media analysis. |
|
|
68
|
+
| **this project**<br>`resolve-ext` | pip | 10 | The gaps the other two leave — see the table below. |
|
|
69
|
+
|
|
70
|
+
All three talk to the same Resolve through the same **single-client** scripting
|
|
71
|
+
API. They coexist without overlap; registering one and wondering why a
|
|
72
|
+
capability is missing is the usual disappointment.
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# 1. Blackmagic's — already on disk, just register it:
|
|
76
|
+
# %ProgramFiles%\Blackmagic Design\DaVinci Resolve\ResolveMCP.exe (Windows)
|
|
77
|
+
# /Applications/DaVinci Resolve/DaVinci Resolve.app/Contents/Applications/ResolveMCP
|
|
78
|
+
# /opt/resolve/bin/ResolveMCP (Linux)
|
|
79
|
+
|
|
80
|
+
# 2. the community server (use --clients manual when driving it non-interactively):
|
|
81
|
+
npx davinci-resolve-mcp setup
|
|
82
|
+
|
|
83
|
+
# 3. this project:
|
|
84
|
+
pip install https://github.com/CaptRauti/ResolveMCP-ext/releases/download/v1.0.0/resolve_ext-1.0.0-py3-none-any.whl
|
|
85
|
+
resolve-ext doctor
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Full procedure, per-platform paths, the Windows "Resolve is on another drive"
|
|
89
|
+
case, and the failure modes of each: **[AGENTS.md](AGENTS.md)** — written so an
|
|
90
|
+
AI agent can install all three unattended.
|
|
91
|
+
|
|
92
|
+
## Why this exists
|
|
93
|
+
|
|
94
|
+
DaVinci Resolve **21.1.2** (22 Oct 2026) added a batch of scripting APIs. The
|
|
95
|
+
popular MCP servers do not implement them — verified two ways: searching their
|
|
96
|
+
source for each symbol, and probing live Resolve objects with `hasattr` (a type
|
|
97
|
+
stub documents the API; only the live object proves your build has it).
|
|
98
|
+
|
|
99
|
+
| Resolve 21.1.2 symbol | In mainstream MCP servers |
|
|
100
|
+
|---|---|
|
|
101
|
+
| `Resolve.GetEffectsList` | absent |
|
|
102
|
+
| `TimelineItem.AddEffect` / `GetEffects` / `DeleteEffects` | absent |
|
|
103
|
+
| `Effect.GetInfo` / `Get/SetIsEnabled` / `Get/SetIndex` | absent |
|
|
104
|
+
| `TimelineItem.CopyAttributes` | absent |
|
|
105
|
+
| `TimelineItem.ExportFrame` / `MediaPoolItem.ExportFrame` | absent |
|
|
106
|
+
| `MediaPoolItem.GetParentFolder` / `Folder.GetParentFolder` | absent |
|
|
107
|
+
| `TimelineItem.Get/SetLeftOffset` / `GetPosition` | absent |
|
|
108
|
+
| `Timeline.MoveItems` / `DeleteGaps` / `Get/SetIsEnabled` | absent |
|
|
109
|
+
| **Photo Album APIs** (`PhotoAlbum`, `PhotoAlbumItem`, 53 methods) | absent |
|
|
110
|
+
| `Graph.SetNodeLabel` / `Get/SetNodeColor` / `SetNodeEnabled` | absent |
|
|
111
|
+
| `Project.GetAudioRenderFormats` / `GetAudioRenderCodecs` | absent |
|
|
112
|
+
| `Project.UpdateRenderPreset` / `SetQuickExportEnabledForRenderPreset` | absent |
|
|
113
|
+
| `MediaPoolItem.SetTranscription` | absent |
|
|
114
|
+
|
|
115
|
+
`TimelineItem` alone exposes **113 methods** on 21.1.2; a large share of them
|
|
116
|
+
were unreachable through MCP before this server.
|
|
117
|
+
|
|
118
|
+
## Install
|
|
119
|
+
|
|
120
|
+
> **Installing this with an AI agent?** Point it at
|
|
121
|
+
> [AGENTS.md](AGENTS.md) — it is a step-by-step procedure written for agents,
|
|
122
|
+
> including the failure cases. This section is the human-facing version.
|
|
123
|
+
|
|
124
|
+
Requires **Python 3.10+** and **DaVinci Resolve Studio** (external scripting is
|
|
125
|
+
a Studio feature — the free build refuses it).
|
|
126
|
+
|
|
127
|
+
### From a release (recommended)
|
|
128
|
+
|
|
129
|
+
Download the wheel from the [releases page][releases] and install it:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
pip install resolve_ext-1.0.0-py3-none-any.whl
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
The wheel is pure Python and platform-independent — the same file works on
|
|
136
|
+
Windows, macOS and Linux.
|
|
137
|
+
|
|
138
|
+
### From a git checkout
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
pip install git+https://github.com/CaptRauti/ResolveMCP-ext
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Or from a local clone:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
git clone https://github.com/CaptRauti/ResolveMCP-ext
|
|
148
|
+
cd ResolveMCP-ext
|
|
149
|
+
pip install .
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
`mcp` 2.x breaks FastMCP, so the dependency is pinned `<2`. That pin is in the
|
|
153
|
+
package metadata — you do not need to add it yourself.
|
|
154
|
+
|
|
155
|
+
### Verify the installation
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
resolve-ext doctor
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
resolve-ext 1.0.0
|
|
163
|
+
|
|
164
|
+
--- Python ---
|
|
165
|
+
3.12.4 at /usr/bin/python3
|
|
166
|
+
ok (3.10+)
|
|
167
|
+
|
|
168
|
+
--- DaVinci Resolve scripting API ---
|
|
169
|
+
platform linux
|
|
170
|
+
api_path /opt/resolve/Developer/Scripting (found)
|
|
171
|
+
lib_path /opt/resolve/libs/fusionscript.so (found)
|
|
172
|
+
|
|
173
|
+
--- Import test ---
|
|
174
|
+
ok: DaVinciResolveScript imports
|
|
175
|
+
|
|
176
|
+
--- Live connection ---
|
|
177
|
+
ok: connected to Resolve; project = My Project
|
|
178
|
+
|
|
179
|
+
--- Result ---
|
|
180
|
+
All checks passed.
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
`doctor` exits `0` when everything works and `1` otherwise, so it is usable in
|
|
184
|
+
a setup script. When something is wrong it names the exact path that is missing
|
|
185
|
+
and what to enable.
|
|
186
|
+
|
|
187
|
+
### Register with your MCP client
|
|
188
|
+
|
|
189
|
+
The server finds Resolve's scripting API by itself, so on a default
|
|
190
|
+
installation there is **nothing to configure**:
|
|
191
|
+
|
|
192
|
+
```json
|
|
193
|
+
{
|
|
194
|
+
"mcpServers": {
|
|
195
|
+
"resolve-ext": { "command": "resolve-ext" }
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
For a non-standard Resolve location, or to see the explicit form, run:
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
resolve-ext env
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
which prints the configuration for the machine it runs on — including the exact
|
|
207
|
+
`RESOLVE_SCRIPT_API`, `RESOLVE_SCRIPT_LIB` and `PYTHONPATH` values — as generic
|
|
208
|
+
JSON and as a `hermes mcp add` command.
|
|
209
|
+
|
|
210
|
+
When merging into an existing client config, the server entry sits **flat**
|
|
211
|
+
under the client's own `mcpServers` key. Do not wrap it in a second
|
|
212
|
+
`mcpServers` level — that silently disables tool discovery.
|
|
213
|
+
|
|
214
|
+
### Resolve settings
|
|
215
|
+
|
|
216
|
+
Resolve must be running with **Preferences → System → General → External
|
|
217
|
+
scripting using → Local**.
|
|
218
|
+
|
|
219
|
+
[releases]: https://github.com/CaptRauti/ResolveMCP-ext/releases
|
|
220
|
+
|
|
221
|
+
## Tools
|
|
222
|
+
|
|
223
|
+
### `effects`
|
|
224
|
+
|
|
225
|
+
```
|
|
226
|
+
list_available(media_type?) video (default) | photo | audio
|
|
227
|
+
list_applied(item_id?|name?, category?)
|
|
228
|
+
apply(item_id?|name?, category, id)
|
|
229
|
+
remove(item_id?|name?, category, indices?)
|
|
230
|
+
set_enabled(item_id?|name?, index, enabled, category?)
|
|
231
|
+
reorder(item_id?|name?, index, new_index, category?)
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Address a timeline item with `item_id` (`GetUniqueId`) or with `name` plus the
|
|
235
|
+
track it sits on (`track_type` default `video`, `track_index` default `1`).
|
|
236
|
+
|
|
237
|
+
Always call `list_available` first — `apply` needs the `category` and `id` it
|
|
238
|
+
returns. On a stock 21.1.2 Studio that is **36 fusion + 98 ofx** effects for
|
|
239
|
+
`video`, plus the Fairlight set for `audio`. Fusion ids look like
|
|
240
|
+
`Fusion/Effects/<category>/<name>`.
|
|
241
|
+
|
|
242
|
+
### `clip_attrs`
|
|
243
|
+
|
|
244
|
+
```
|
|
245
|
+
options() the full option vocabulary
|
|
246
|
+
copy(source_id?|source_name?, target_ids?|target_names?, options, ...)
|
|
247
|
+
read(item_id?|name?)
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
The API behind the Edit page's **Paste Attributes** — the lever that makes a
|
|
251
|
+
per-camera look repeatable: grade one clip, push the same attributes onto every
|
|
252
|
+
other clip from that camera.
|
|
253
|
+
|
|
254
|
+
36 options in 6 groups (`clip`, `video`, `zoom_crop_flip`, `retime`,
|
|
255
|
+
`grade_fusion`, `audio`). Shorthands: `copy_all_video`, `copy_all_audio`.
|
|
256
|
+
|
|
257
|
+
```json
|
|
258
|
+
{"colorCorrection": true, "allZoomAttributes": true,
|
|
259
|
+
"allCropAttributes": true, "videoPlugins": true}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### `export_frame`
|
|
263
|
+
|
|
264
|
+
```
|
|
265
|
+
item(item_id?|name?, frame_offset, output_type?, file_path?, format?, max_dimension?)
|
|
266
|
+
media(clip_id, frame_offset, output_type?, file_path?, format?, max_dimension?)
|
|
267
|
+
timeline(frame_offset, ...) # broken on 21.1.2 — see below
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
`item` and `media` are verified working. The offset is 0-based and must be
|
|
271
|
+
below the item's duration. `output_type`: `file` (needs `file_path`) or
|
|
272
|
+
`encodedString` (default); for the latter the base64 is decoded and written to
|
|
273
|
+
`out/` next to the server, with only a short prefix returned inline.
|
|
274
|
+
|
|
275
|
+
### `folder_ops`
|
|
276
|
+
|
|
277
|
+
```
|
|
278
|
+
parent_of_clip(clip_id) which bin holds this clip
|
|
279
|
+
parent_of_folder(path?|folder_id?) a folder's parent (root has none)
|
|
280
|
+
path(clip_id?|folder_id?) "Master/Sub/Bin"
|
|
281
|
+
find_clip(clip_id) locate a clip anywhere in the tree
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### `photo_albums`
|
|
285
|
+
|
|
286
|
+
```
|
|
287
|
+
list() / current() / set_current(album_id?|name?)
|
|
288
|
+
create(name) / create_from_photos(name, clip_ids) / delete(album_ids?) / rename(...)
|
|
289
|
+
quick_export_presets() / render_settings(settings?)
|
|
290
|
+
photos(album_id?|name?) / add_photos(clip_ids) / remove_photos(photo_ids) / current_item()
|
|
291
|
+
stills(album_id?|name?, all_photos?)
|
|
292
|
+
photo(photo_id) / rate(photo_id, stars) / flag(photo_id, color?|clear?)
|
|
293
|
+
clip_color(photo_id, color?|clear?) / versions(photo_id)
|
|
294
|
+
add_version / load_version / delete_version
|
|
295
|
+
copy_grade / effects_list / effect_apply / effect_remove / fusion_comps / fusion_add
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Verified working on 21.1.2.10: **9/9 `PhotoAlbum` methods, 20/20
|
|
299
|
+
`PhotoAlbumItem` methods**, `CreatePhotoAlbumFromPhotos`, persistent ratings /
|
|
300
|
+
flags / versions, `AddFusionComp` on a photo.
|
|
301
|
+
|
|
302
|
+
`add_photos` is **broken upstream** (`AddToPhotoAlbum` adds nothing) — the
|
|
303
|
+
action verifies by reading the album back and tells you so. Use
|
|
304
|
+
**`create_from_photos`**, which is the verified path.
|
|
305
|
+
|
|
306
|
+
> Note the split: the album *listing* methods live on `Project`, the
|
|
307
|
+
> *creating/adding* ones on `MediaPool`, and Resolve answers none of them. The
|
|
308
|
+
> tool resolves the right object per call, because a Resolve proxy makes
|
|
309
|
+
> `hasattr` report every method on every object.
|
|
310
|
+
|
|
311
|
+
### `node_graph`
|
|
312
|
+
|
|
313
|
+
```
|
|
314
|
+
info(item_id?|name?|photo_id?) node list with labels and colours
|
|
315
|
+
label(node_index, label) "" clears the label
|
|
316
|
+
color(node_index, color) "" clears the colour
|
|
317
|
+
enable(node_index, enabled)
|
|
318
|
+
tools(node_index) which Fusion/OFX tools sit in the node
|
|
319
|
+
lut(node_index, lut_path?) read, or set
|
|
320
|
+
cache(node_index, mode?) read, or set
|
|
321
|
+
reset_colors() photo graphs only
|
|
322
|
+
reset_grades(confirm=true) destructive — clears the whole grade
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Valid node colours: Orange, Apricot, Yellow, Lime, Olive, Green, Teal, Navy,
|
|
326
|
+
Blue, Purple, Violet, Pink, Tan, Beige, Brown, Chocolate.
|
|
327
|
+
|
|
328
|
+
Target a timeline clip by `item_id`/`name`, or a photo by `photo_id`. Labels
|
|
329
|
+
and colours verified persistent across calls.
|
|
330
|
+
|
|
331
|
+
### `project_extras`
|
|
332
|
+
|
|
333
|
+
```
|
|
334
|
+
capabilities() what this build answers at all
|
|
335
|
+
render_presets() GetRenderPresetList
|
|
336
|
+
update_render_preset(preset_name) GUI-dependent, see the broken table
|
|
337
|
+
quick_export_enable(preset_name, enabled) GUI-dependent
|
|
338
|
+
audio_formats() {display name -> extension}
|
|
339
|
+
audio_codecs(format_extension) {description -> codec name}
|
|
340
|
+
transcription(clip_id, use_nested?) a clip's transcript
|
|
341
|
+
set_transcription(clip_id, transcription) verified by reading back
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
`audio_formats` / `audio_codecs` VERIFIED: 7 real formats
|
|
345
|
+
(`FLAC, MP3, MP4, MXF OP-Atom, MXF OP1A (IMF), QuickTime, Wave`) and their
|
|
346
|
+
codecs. `render_presets` VERIFIED: 37 real presets.
|
|
347
|
+
|
|
348
|
+
### `camera_comparison`
|
|
349
|
+
|
|
350
|
+
```
|
|
351
|
+
detect() which camera each clip is (from its name)
|
|
352
|
+
grid_2x2() four cameras in quadrants, scale 0.5
|
|
353
|
+
split_h() / split_v() two-up, side by side or stacked
|
|
354
|
+
layout(assignments) explicit {clip_id: {x, y, scale}} — fractions of raster
|
|
355
|
+
fullframe(clip_ids) back to 1.0 zoom, centred
|
|
356
|
+
markers() timeline markers naming each camera
|
|
357
|
+
sort_to_bins() file clips into per-camera bins by name
|
|
358
|
+
build_timeline() one video track per camera, scenes synced
|
|
359
|
+
read() current transform of every clip
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
Camera detection, longest prefix first:
|
|
363
|
+
|
|
364
|
+
| prefix | camera | marker |
|
|
365
|
+
|---|---|---|
|
|
366
|
+
| `ace*` | Insta360 AcePro | Orange |
|
|
367
|
+
| `go*` | Insta360 Go Ultra | Green |
|
|
368
|
+
| `luna*` | Insta360 Luna Ultra | Cyan |
|
|
369
|
+
| `x*` | Insta360 X5 | Blue |
|
|
370
|
+
| `vid_*` | Handy (Android file) | Gray |
|
|
371
|
+
|
|
372
|
+
**Why this tool exists:** the loose scripts this replaces positioned clips
|
|
373
|
+
with `SetProperty("PositionX", …)` / `"PositionY"`. MEASURED on 21.1.2.10 those
|
|
374
|
+
keys return **`False` and are silently ignored** — `GetProperty` gives back
|
|
375
|
+
`False` too. Zoom worked, the positioning never did. The real keys are **`Pan`**
|
|
376
|
+
(x, +right) and **`Tilt`** (y, **+up**), in pixels. Every write here is read
|
|
377
|
+
back, and a transform that does not stick is reported as a failure.
|
|
378
|
+
|
|
379
|
+
Verified: on a 3840x2160 timeline `grid_2x2` puts AcePro at `Pan -960, Tilt
|
|
380
|
+
540` (top-left) and Go Ultra at `Pan 960, Tilt 540` (top-right), read back.
|
|
381
|
+
|
|
382
|
+
### `clip_position`
|
|
383
|
+
|
|
384
|
+
```
|
|
385
|
+
get(item_id?|name?) position + left/right handle offsets (safe, read-only)
|
|
386
|
+
enabled() timeline enabled state
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
`set` / `move` / `set_offset` / `delete_gaps` exist in the code but are
|
|
390
|
+
**disabled and transparent about why** — they wrap Resolve calls that are broken
|
|
391
|
+
or destructive on 21.1.2. See the next section.
|
|
392
|
+
|
|
393
|
+
## Measured behaviour on Resolve 21.1.2.10
|
|
394
|
+
|
|
395
|
+
Everything below was measured against a live Studio build, not read from docs.
|
|
396
|
+
`tests/` reproduces all of it — 132 assertions across four suites, 0 failures.
|
|
397
|
+
|
|
398
|
+
**Working**
|
|
399
|
+
|
|
400
|
+
| Call | Result |
|
|
401
|
+
|---|---|
|
|
402
|
+
| `Resolve.GetEffectsList` | 36 fusion + 98 ofx (video), Fairlight (audio) |
|
|
403
|
+
| `TimelineItem.AddEffect` / `GetEffects` / `DeleteEffects` | verified end to end |
|
|
404
|
+
| `Effect.GetInfo` / `GetIsEnabled` / `SetIsEnabled` / `GetIndex` | verified |
|
|
405
|
+
| `TimelineItem.CopyAttributes` | verified with changed source values |
|
|
406
|
+
| `TimelineItem.ExportFrame` | offsets `0..duration-1` succeed, beyond is refused |
|
|
407
|
+
| `MediaPoolItem.ExportFrame` | verified, file written |
|
|
408
|
+
| `MediaPoolItem` / `Folder.GetParentFolder` | verified |
|
|
409
|
+
| `Timeline.GetIsEnabled` / `SetIsEnabled` | verified |
|
|
410
|
+
|
|
411
|
+
**Broken or destructive — do not use**
|
|
412
|
+
|
|
413
|
+
| Call | Measured behaviour |
|
|
414
|
+
|---|---|
|
|
415
|
+
| `TimelineItem.SetPosition` | Returns `False` for **every** input — real video clips and stills, int and float `startFrame`, a no-op equal to the current frame, both `moveLinkedItems` values. `Timeline.MoveItems` behaves identically, while `SetCurrentTimecode` succeeds in the same session. |
|
|
416
|
+
| `Timeline.ExportFrame` | **Works** — `frameOffset` is relative to the timeline start, valid `0..duration-1`. Verified on a 480-frame timeline: 0..479 succeed, 480 is refused. An absolute frame is always refused, which reads like a failure. An earlier release listed this as broken; that was a bad measurement on a timeline whose metadata contradicted its contents. |
|
|
417
|
+
| `Timeline.DeleteGaps` | **Works** — verified `[86400, 86600, 86800]` → `[86400, 86520, 86640]`, gaps closed. It does nothing on a timeline that reports `StartFrame == EndFrame` while holding clips; see the `AppendToTimeline` entry below. |
|
|
418
|
+
| `TimelineItem.SetLeftOffset` | **Destructive.** On a clip at frame 400 with duration 120, `SetLeftOffset(5)` moved it to **-85995** and inflated the duration to **86515** frames: it *extends the clip from source media*, it does not nudge the edit point. Setting the offset back to `0` did **not** restore the original position (the clip stayed at -86000). There is no API-level undo. |
|
|
419
|
+
| `MediaPool.AddToPhotoAlbum` | Adds nothing and returns an empty list. Tested four ways: immediately after `CreateEmptyPhotoAlbum`, with a 2 s pause, after an explicit `SetCurrentPhotoAlbum` that `GetCurrentPhotoAlbum` confirmed, and with a fresh album each time — zero photos every run. `MediaPool.CreatePhotoAlbumFromPhotos` VERIFIED working (photos land and read back), so use that. |
|
|
420
|
+
| `Graph.ResetAllNodeColors` | Exists on a photo album item's graph but **not** on a timeline clip's graph (`NoneType is not callable`). Clearing a colour with `SetNodeColor(i, "")` works on both. |
|
|
421
|
+
| `MediaPoolItem.GetNodeGraph` | Not callable on 21.1.2.10 (while `TimelineItem.GetNodeGraph` and `PhotoAlbumItem.GetNodeGraph` both work). Put the clip on a timeline and address it by `item_id`. |
|
|
422
|
+
| `Project.UpdateRenderPreset` | Returns `False` for all 37 factory presets that `GetRenderPresetList` reports (`'H.264 Master'` included). `Project.SaveRenderPreset` does not exist in the API, so a user preset cannot be created from scripting to test against. GUI-dependent at best. |
|
|
423
|
+
| `Project.SetQuickExportEnabledForRenderPreset` | Same shape: `False` for a factory preset. |
|
|
424
|
+
| `MediaPoolItem.SetTranscription` | Returns `False` for a PNG still **and** for a real video clip with no transcript, with both `'01:00:00:00'` and `'00:00:00:00'` start timecodes. It *replaces* a transcript, so it appears unable to create one where none exists. |
|
|
425
|
+
| `MediaPoolItem.SetClipColor` (photo items) | Returned `False` for `'Sky'` on a photo album item while the colour list is documented; `ClearClipColor` works. The action reports the rejection rather than assuming success. |
|
|
426
|
+
| `Folder.AddSubFolder` / `Folder.MoveClips` | Do not exist. Both live on **MediaPool** (`mp.AddSubFolder(parent, name)`, `mp.MoveClips(clips, folder)`) — verified that `MoveClips` really relocates the clip and returns `True`, not a false success. Same object-split as the photo-album API. |
|
|
427
|
+
| `TimelineItem.SetProperty("PositionX"/"PositionY")` | **Silently ignored** — both the setter and the getter return `False`. Use `Pan` / `Tilt` (pixels, +x right, +y up). This is what broke the project's own grid scripts: zoom applied, positions never did. |
|
|
428
|
+
| `TimelineItem.AddMarker(0)` on the clip at timeline position 0 | Returns `True` and writes nothing — `tl.GetMarkers()` shows the count unchanged and no marker by that name. **`Timeline.AddMarker(0)` works.** Markers are therefore placed through the timeline, never through the item. An earlier release blamed `Timeline.AddMarker`; that was the same bad setup as above. |
|
|
429
|
+
| `AppendToTimeline` with `recordFrame` below `StartFrame` | **Silently accepted.** Passing `0/200/400` on a timeline that starts at `86400` places the clips at 0/200/400 — outside the timeline's own range — and the timeline's metadata is not reconciled: it reports `StartFrame == EndFrame` (duration 0) while holding three clips. It survives `SetProperty`, closing and reopening. On such a timeline `DeleteGaps` and `SetLeftOffset` do nothing and `Timeline.ExportFrame` refuses every offset, so it looks like three broken APIs. Assert `recordFrame >= GetStartFrame()`. |
|
|
430
|
+
| `AppendToTimeline` with `startFrame` == `endFrame` | Returns `None` and places nothing. With single-frame media (`GetClipProperty("Frames") == "1"`) the natural `endFrame` is `0`, so a "correct" 0..0 range silently fails. Omitting `endFrame` lets Resolve use the whole clip and works. |
|
|
431
|
+
|
|
432
|
+
**Gotchas**
|
|
433
|
+
|
|
434
|
+
- `TimelineItem.GetProperty("Position")` returns a **bool** on 21.1.2, not
|
|
435
|
+
coordinates. The position lives in **`Pan` / `Tilt`** (x/y in pixels).
|
|
436
|
+
`RotationAngle` is degrees, `Opacity` 0–100, crop values 0–1, `ZoomX/Y` a
|
|
437
|
+
multiplier. All are writable — verify with a read-back.
|
|
438
|
+
- Setting a timeline's `SetIsEnabled(False)` makes `GetCurrentTimeline()` return
|
|
439
|
+
`None`, so every later call reports "No current timeline" until it is switched
|
|
440
|
+
back on in the GUI.
|
|
441
|
+
- `MediaPoolItem.GetParentFolder` is only useful with a recursive walk of the
|
|
442
|
+
media pool: `ImportMedia` drops clips into the currently *selected* bin, so a
|
|
443
|
+
root-only lookup reports "not found" while the clip exists one level down.
|
|
444
|
+
|
|
445
|
+
## Testing
|
|
446
|
+
|
|
447
|
+
Two layers, because DaVinci Resolve cannot run in CI.
|
|
448
|
+
|
|
449
|
+
### Offline — no Resolve needed, this is what CI runs
|
|
450
|
+
|
|
451
|
+
```bash
|
|
452
|
+
python tests/test_offline.py
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
Checks that `server.py` imports **without** Resolve present (so `pip install`
|
|
456
|
+
followed by `resolve-ext --help` cannot fail), that all ten tools register,
|
|
457
|
+
that the CLI returns the right exit codes, and that the platform path
|
|
458
|
+
resolution produces the correct paths for the OS it finds itself on. Runs on
|
|
459
|
+
Windows, macOS and Linux — see `.github/workflows/test.yml`.
|
|
460
|
+
|
|
461
|
+
### Live — needs a running Resolve Studio
|
|
462
|
+
|
|
463
|
+
```bash
|
|
464
|
+
python tests/test_e2e.py # core tools
|
|
465
|
+
python tests/test_phase1.py # photo albums + node graph
|
|
466
|
+
python tests/test_extras.py # render presets, audio formats, transcription
|
|
467
|
+
python tests/test_camera.py # camera layouts, labels, markers, sorting
|
|
468
|
+
|
|
469
|
+
# point at your own media if ~/Pictures has fewer than 3 PNGs
|
|
470
|
+
RESOLVE_EXT_TEST_MEDIA="D:/stills/*.png" python tests/test_camera.py
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
**162 assertions, 0 failures** against Resolve Studio 21.1.2.10
|
|
474
|
+
(30 offline + 132 live).
|
|
475
|
+
|
|
476
|
+
Each suite creates its own scratch project, exercises the tools and deletes it
|
|
477
|
+
again — they never touch your other projects. Two habits are deliberate:
|
|
478
|
+
|
|
479
|
+
- Assertions check that values **changed and read back**, not merely that no
|
|
480
|
+
error was returned. A write test that passes because nothing moved proves
|
|
481
|
+
nothing; that is exactly how `DeleteGaps` and `AddToPhotoAlbum` were caught
|
|
482
|
+
reporting success while doing nothing.
|
|
483
|
+
- Known-broken calls are asserted to *fail honestly*. If a future Resolve build
|
|
484
|
+
fixes `AddToPhotoAlbum`, its test will start failing — which is the signal to
|
|
485
|
+
re-measure and update this README, not a broken test.
|
|
486
|
+
|
|
487
|
+
## Design notes
|
|
488
|
+
|
|
489
|
+
- **Extension, not fork.** Nothing here duplicates a mainstream server's
|
|
490
|
+
surface, so both can run side by side without the model having to choose.
|
|
491
|
+
- **Broken APIs are reported, not hidden.** Where Resolve's own call lies
|
|
492
|
+
(`DeleteGaps` returning `True`), the tool says so with the measurement
|
|
493
|
+
attached, rather than emitting a success the caller cannot trust.
|
|
494
|
+
- **Destructive APIs are not exposed.** `SetLeftOffset` cannot be undone through
|
|
495
|
+
the API, so it stays out of the surface and `get` remains read-only.
|
|
496
|
+
|
|
497
|
+
## Requirements
|
|
498
|
+
|
|
499
|
+
- DaVinci Resolve **Studio 21.1.2+** (earlier builds lack these symbols)
|
|
500
|
+
- Windows / macOS / Linux wherever the Resolve scripting API is available
|
|
501
|
+
- Python 3.10+ and `mcp[cli]>=1.30,<2`
|
|
502
|
+
|
|
503
|
+
## License
|
|
504
|
+
|
|
505
|
+
MIT — see [LICENSE](LICENSE).
|