davinci-resolve-mcp 2.87.2 → 2.89.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +122 -0
- package/README.md +4 -4
- package/README.zh-CN.md +5 -5
- package/docs/SKILL.md +11 -2
- package/docs/contributing.md +1 -1
- package/docs/install.md +3 -3
- package/docs/reference/api-coverage.md +2 -2
- package/install.py +1 -1
- package/package.json +1 -1
- package/src/analysis_dashboard.py +2 -1
- package/src/granular/common.py +36 -2
- package/src/granular/folder.py +26 -15
- package/src/granular/media_pool_item.py +67 -21
- package/src/granular/project.py +29 -6
- package/src/granular/resolve_control.py +187 -4
- package/src/granular/timeline.py +37 -4
- package/src/granular/timeline_item.py +1 -1
- package/src/resolve_mcp_server.py +1 -1
- package/src/server.py +40 -18
- package/src/utils/cloud_operations.py +5 -3
- package/src/utils/object_inspection.py +4 -2
- package/src/utils/project_properties.py +3 -1
- package/src/utils/resolve_probe.py +68 -0
- package/src/utils/resolve_versions.py +108 -6
- package/src/utils/timeline_kernel_live_probe.py +3 -1
- package/src/utils/timeline_versioning.py +4 -2
|
@@ -102,11 +102,13 @@ def at_least(live: Any, required: Any) -> Optional[bool]:
|
|
|
102
102
|
# measured from what a reporter measured, because the two carry different
|
|
103
103
|
# weight and an agent relaying the fact should be able to say which it is.
|
|
104
104
|
#
|
|
105
|
-
# measured
|
|
106
|
-
# reported
|
|
107
|
-
# vendor
|
|
105
|
+
# measured — probed live by this project
|
|
106
|
+
# reported — a user's live probe, credited, not independently reproduced
|
|
107
|
+
# vendor — stated by Blackmagic's shipped Developer/Scripting/README.txt
|
|
108
|
+
# documented — a floor this server already enforces at the call site, taken
|
|
109
|
+
# from Blackmagic's release documentation. See CODE_FLOORS.
|
|
108
110
|
|
|
109
|
-
|
|
111
|
+
_EVIDENCE_GATES: List[Dict[str, Any]] = [
|
|
110
112
|
{
|
|
111
113
|
"symbol": "Resolve.GetFairlightPresets",
|
|
112
114
|
"introduced_in": "20.2.2",
|
|
@@ -117,7 +119,11 @@ VERSION_GATES: List[Dict[str, Any]] = [
|
|
|
117
119
|
"issue": 128,
|
|
118
120
|
},
|
|
119
121
|
{
|
|
120
|
-
|
|
122
|
+
# Named on Project, not Timeline: the shipped README lists it in the
|
|
123
|
+
# Project section and src/server.py calls it on the project handle. The
|
|
124
|
+
# method's name says "CurrentTimeline", which is what made the earlier
|
|
125
|
+
# Timeline attribution here look right.
|
|
126
|
+
"symbol": "Project.ApplyFairlightPresetToCurrentTimeline",
|
|
121
127
|
"introduced_in": "20.2.2",
|
|
122
128
|
"source": "measured",
|
|
123
129
|
"note": "Same gate as GetFairlightPresets; the two are only useful together.",
|
|
@@ -166,6 +172,90 @@ VERSION_GATES: List[Dict[str, Any]] = [
|
|
|
166
172
|
},
|
|
167
173
|
]
|
|
168
174
|
|
|
175
|
+
|
|
176
|
+
# ── Floors this server already enforces ─────────────────────────────────────
|
|
177
|
+
# `_requires_method(obj, "Method", "X.Y")` in src/server.py and src/granular/
|
|
178
|
+
# refuses a call when the method is absent and names a floor. Those floors were
|
|
179
|
+
# real routing knowledge that `check_version_support` could not see: the ledger
|
|
180
|
+
# above knew seven symbols while the two servers gated thirty-one, so the one
|
|
181
|
+
# call an agent is told to make answered `unknown` for surfaces this repo was
|
|
182
|
+
# already routing on. That is the shape of issue #132 — guidance that does not
|
|
183
|
+
# vary with the build it is connected to — showing up inside our own tooling.
|
|
184
|
+
#
|
|
185
|
+
# The `documented` label is deliberately weaker than `measured`. These floors
|
|
186
|
+
# come from Blackmagic's release documentation (the build whose shipped
|
|
187
|
+
# Developer/Scripting README first carried the method), not from a live bisect
|
|
188
|
+
# by this project, so an agent relaying one should say "documented as requiring
|
|
189
|
+
# 21.0.4+", never "measured absent on your build". A floor being wrong in the
|
|
190
|
+
# vendor's direction is survivable — the call-site guard still probes the live
|
|
191
|
+
# object before refusing, so the floor only ever supplies the *explanation*.
|
|
192
|
+
#
|
|
193
|
+
# tests/test_version_gate_drift.py fails when a call site and this table
|
|
194
|
+
# disagree, which is what keeps the two from drifting apart again.
|
|
195
|
+
|
|
196
|
+
CODE_FLOORS: Dict[str, str] = {
|
|
197
|
+
"MediaPoolItem.LinkProxyMedia": "17.0",
|
|
198
|
+
"MediaPoolItem.LinkFullResolutionMedia": "20.0",
|
|
199
|
+
"MediaPoolItem.MonitorGrowingFile": "20.0",
|
|
200
|
+
"MediaPoolItem.ReplaceClipPreserveSubClip": "20.0",
|
|
201
|
+
"Timeline.GetVoiceIsolationState": "20.1",
|
|
202
|
+
"Timeline.SetVoiceIsolationState": "20.1",
|
|
203
|
+
"TimelineItem.GetVoiceIsolationState": "20.1",
|
|
204
|
+
"TimelineItem.SetVoiceIsolationState": "20.1",
|
|
205
|
+
"MediaPoolItem.SetName": "20.2",
|
|
206
|
+
"TimelineItem.SetName": "20.2",
|
|
207
|
+
"TimelineItem.ResetAllNodeColors": "20.2",
|
|
208
|
+
"Resolve.DisableBackgroundTasksForCurrentResolveSession": "21.0",
|
|
209
|
+
"Project.GenerateSpeech": "21.0",
|
|
210
|
+
"Project.ResetIntellisearchAnalysis": "21.0",
|
|
211
|
+
"MediaPoolItem.PerformAudioClassification": "21.0",
|
|
212
|
+
"MediaPoolItem.ClearAudioClassification": "21.0",
|
|
213
|
+
"MediaPoolItem.AnalyzeForIntellisearch": "21.0",
|
|
214
|
+
"MediaPoolItem.AnalyzeForSlate": "21.0",
|
|
215
|
+
"MediaPoolItem.RemoveMotionBlur": "21.0",
|
|
216
|
+
"Folder.PerformAudioClassification": "21.0",
|
|
217
|
+
"Folder.ClearAudioClassification": "21.0",
|
|
218
|
+
"Folder.AnalyzeForIntellisearch": "21.0",
|
|
219
|
+
"Folder.AnalyzeForSlate": "21.0",
|
|
220
|
+
"Folder.RemoveMotionBlur": "21.0",
|
|
221
|
+
"Resolve.GetLayoutPresetList": "21.0.4",
|
|
222
|
+
"Resolve.GetBurnInPresetList": "21.0.4",
|
|
223
|
+
"Resolve.DeleteBurnInPreset": "21.0.4",
|
|
224
|
+
"Resolve.GetUserPreferencesPresetList": "21.0.4",
|
|
225
|
+
"Resolve.SaveUserPreferencesPreset": "21.0.4",
|
|
226
|
+
"Resolve.LoadUserPreferencesPreset": "21.0.4",
|
|
227
|
+
"Resolve.DeleteUserPreferencesPreset": "21.0.4",
|
|
228
|
+
"Resolve.ImportUserPreferencesPreset": "21.0.4",
|
|
229
|
+
"Resolve.ExportUserPreferencesPreset": "21.0.4",
|
|
230
|
+
"ProjectManager.GetProjectAttributesInCurrentFolder": "21.0.4",
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
#: Methods whose call-site guard names no usable floor. Recorded rather than
|
|
234
|
+
#: silently dropped, because "we gate this and cannot say since when" is itself
|
|
235
|
+
#: worth an agent knowing — it is a candidate for the next live bisect.
|
|
236
|
+
UNDATED_FLOORS: Tuple[str, ...] = ("Project.GetRenderSettings",)
|
|
237
|
+
|
|
238
|
+
_EVIDENCE_SYMBOLS = {gate["symbol"] for gate in _EVIDENCE_GATES}
|
|
239
|
+
|
|
240
|
+
VERSION_GATES: List[Dict[str, Any]] = _EVIDENCE_GATES + [
|
|
241
|
+
{
|
|
242
|
+
"symbol": symbol,
|
|
243
|
+
"introduced_in": floor,
|
|
244
|
+
"source": "documented",
|
|
245
|
+
"note": (
|
|
246
|
+
"Floor enforced at this server's call site, from Blackmagic's "
|
|
247
|
+
"release documentation rather than a live bisect here. The guard "
|
|
248
|
+
"probes the live object first, so this supplies the explanation, "
|
|
249
|
+
"not the refusal."
|
|
250
|
+
),
|
|
251
|
+
"issue": 132,
|
|
252
|
+
}
|
|
253
|
+
for symbol, floor in sorted(CODE_FLOORS.items())
|
|
254
|
+
# An evidence-backed entry always wins: it says how we know, and these
|
|
255
|
+
# would overwrite that with the weaker label.
|
|
256
|
+
if symbol not in _EVIDENCE_SYMBOLS
|
|
257
|
+
]
|
|
258
|
+
|
|
169
259
|
_GATES_BY_SYMBOL = {gate["symbol"].lower(): gate for gate in VERSION_GATES}
|
|
170
260
|
|
|
171
261
|
AVAILABLE = "available"
|
|
@@ -174,12 +264,24 @@ UNKNOWN = "unknown"
|
|
|
174
264
|
|
|
175
265
|
|
|
176
266
|
def gate_for(symbol: str) -> Optional[Dict[str, Any]]:
|
|
177
|
-
"""The recorded gate for `symbol`, matched loosely. None if we have none.
|
|
267
|
+
"""The recorded gate for `symbol`, matched loosely. None if we have none.
|
|
268
|
+
|
|
269
|
+
Tried in order — exact, then bare method name, then substring — because the
|
|
270
|
+
registry now carries the same method on several classes (`SetName` is gated
|
|
271
|
+
on four). Iteration order decided which one a bare `"SetName"` lookup hit
|
|
272
|
+
when the registry was small enough for that not to matter; it now is not.
|
|
273
|
+
A bare name matching several classes only stays honest while their floors
|
|
274
|
+
agree, which `test_version_gate_drift` asserts.
|
|
275
|
+
"""
|
|
178
276
|
if not symbol:
|
|
179
277
|
return None
|
|
180
278
|
needle = symbol.strip().lower()
|
|
181
279
|
if needle in _GATES_BY_SYMBOL:
|
|
182
280
|
return _GATES_BY_SYMBOL[needle]
|
|
281
|
+
suffix = "." + needle
|
|
282
|
+
for key, gate in _GATES_BY_SYMBOL.items():
|
|
283
|
+
if key.endswith(suffix):
|
|
284
|
+
return gate
|
|
183
285
|
for key, gate in _GATES_BY_SYMBOL.items():
|
|
184
286
|
if needle in key or key in needle:
|
|
185
287
|
return gate
|
|
@@ -17,6 +17,8 @@ import platform
|
|
|
17
17
|
import subprocess
|
|
18
18
|
import sys
|
|
19
19
|
import tempfile
|
|
20
|
+
|
|
21
|
+
from src.utils.resolve_probe import has_method
|
|
20
22
|
import time
|
|
21
23
|
import types
|
|
22
24
|
import traceback
|
|
@@ -153,7 +155,7 @@ def _frame_int(value) -> int:
|
|
|
153
155
|
|
|
154
156
|
|
|
155
157
|
def _source_start(item) -> int:
|
|
156
|
-
if
|
|
158
|
+
if has_method(item, "GetSourceStartFrame"):
|
|
157
159
|
try:
|
|
158
160
|
value = item.GetSourceStartFrame()
|
|
159
161
|
if value is not None:
|
|
@@ -30,6 +30,8 @@ import os
|
|
|
30
30
|
import re
|
|
31
31
|
import time
|
|
32
32
|
import uuid
|
|
33
|
+
|
|
34
|
+
from src.utils.resolve_probe import has_method
|
|
33
35
|
from typing import Any, Dict, List, Optional, Tuple
|
|
34
36
|
|
|
35
37
|
from src.utils import actor_identity, timeline_brain_db
|
|
@@ -280,12 +282,12 @@ def _resolve_media_pool_item_id(item: Any) -> Optional[str]:
|
|
|
280
282
|
"""
|
|
281
283
|
try:
|
|
282
284
|
mpi = item.GetMediaPoolItem()
|
|
283
|
-
if
|
|
285
|
+
if has_method(mpi, "GetUniqueId"):
|
|
284
286
|
return str(mpi.GetUniqueId())
|
|
285
287
|
except Exception:
|
|
286
288
|
pass
|
|
287
289
|
try:
|
|
288
|
-
return str(item.GetUniqueId()) if
|
|
290
|
+
return str(item.GetUniqueId()) if has_method(item, "GetUniqueId") else None
|
|
289
291
|
except Exception:
|
|
290
292
|
return None
|
|
291
293
|
|