davinci-resolve-mcp 2.82.0 → 2.82.1
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 +20 -1
- package/README.md +1 -1
- package/docs/reference/api-limitations.md +21 -3
- package/install.py +1 -1
- package/package.json +1 -1
- package/resolve-advanced/vendor/drp-format/subtitle-style.js +1 -1
- package/src/granular/common.py +1 -1
- package/src/granular/timeline_item.py +27 -1
- package/src/server.py +64 -3
- package/src/utils/api_truth.py +59 -2
- package/src/utils/clip_colors.py +94 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
Release history for the DaVinci Resolve MCP Server. The latest release is summarized in the root README; older entries live here to keep the README focused.
|
|
4
4
|
|
|
5
|
+
## What's New in v2.82.1
|
|
6
|
+
|
|
7
|
+
Corrects the Resolve version the v2.82.0 subtitle-style validation was actually
|
|
8
|
+
run against.
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **v2.82.0 claimed the subtitle-style write path was confirmed on Resolve 21.
|
|
13
|
+
It was confirmed on Resolve Studio 19.1.3.** The validation itself stands —
|
|
14
|
+
Resolve opened the patched track and re-serialised it to its own zstd form
|
|
15
|
+
with the patched values intact — but it was run against 19.1.3, which is the
|
|
16
|
+
build that was installed. Corrected in `api_truth`, the codec header, and the
|
|
17
|
+
changelog. If anything this widens the supported range rather than narrowing
|
|
18
|
+
it, but the version on the claim has to be the one actually tested.
|
|
19
|
+
- **`GetFairlightPresets` / `ApplyFairlightPresetToCurrentTimeline` require
|
|
20
|
+
Resolve 20.2.2+**, now recorded in the `api_truth` Fairlight entry. On 19.1.3
|
|
21
|
+
both are absent (confirmed live), so on older builds the per-parameter gap
|
|
22
|
+
really is the whole story and the preset workaround is unavailable.
|
|
23
|
+
|
|
5
24
|
## What's New in v2.82.0
|
|
6
25
|
|
|
7
26
|
Caption styling, which the scripting API cannot touch at all, is now readable
|
|
@@ -21,7 +40,7 @@ Blackmagic.
|
|
|
21
40
|
position vector (param 17). New codec at
|
|
22
41
|
`resolve-advanced/vendor/drp-format/subtitle-style.js`.
|
|
23
42
|
|
|
24
|
-
Verified live on Resolve
|
|
43
|
+
Verified live on Resolve Studio 19.1.3 (2026-08-06): a patched track opens without error
|
|
25
44
|
and, once Resolve next re-serialises it, is written back out in Resolve's own
|
|
26
45
|
zstd form with the patched values intact — so Resolve genuinely parses the
|
|
27
46
|
write rather than passing the bytes through. Read side verified against a
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# DaVinci Resolve MCP Server
|
|
2
2
|
|
|
3
|
-
[](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
|
|
4
4
|
[](https://www.npmjs.com/package/davinci-resolve-mcp)
|
|
5
5
|
[](docs/reference/api-coverage.md)
|
|
6
6
|
[-blue.svg)](#server-modes)
|
|
@@ -12,7 +12,7 @@ that none exists).
|
|
|
12
12
|
|
|
13
13
|
**Verified on:** DaVinci Resolve Studio 21.0.2
|
|
14
14
|
|
|
15
|
-
**Totals:**
|
|
15
|
+
**Totals:** 27 missing capabilities, 30 bugs / unreliable behaviors.
|
|
16
16
|
|
|
17
17
|
The authoritative source is the runtime-queryable `api_truth` ledger
|
|
18
18
|
(`resolve_control api_truth "<query>"`); this document is generated from
|
|
@@ -131,7 +131,7 @@ equivalent, blocking full automation.
|
|
|
131
131
|
### Fairlight audio levels / pan / EQ / automation / FairlightFX
|
|
132
132
|
|
|
133
133
|
- **Object:** `TimelineItem / Timeline`
|
|
134
|
-
- **Behavior:** There is no API to set clip or track volume, pan, EQ, audio automation, or to add/configure FairlightFX. SetProperty covers video transform only; the audio surface is read-only (GetSourceAudioChannelMapping, GetAudioMapping, voice isolation). Verified via dir() + SetProperty docs AND by live mutating attempt on 21.0.0: SetProperty('Volume'|'Level'|'Gain'|'AudioVolume', 0) all return False (note 'Pan' is the VIDEO transform key, not audio pan, so it misleadingly succeeds). The gap is PER-PARAMETER control specifically: a whole saved mix CAN be applied wholesale via Project.ApplyFairlightPresetToCurrentTimeline(name), with the available names from Resolve.GetFairlightPresets() — so 'no Fairlight write path exists' would be too strong.
|
|
134
|
+
- **Behavior:** There is no API to set clip or track volume, pan, EQ, audio automation, or to add/configure FairlightFX. SetProperty covers video transform only; the audio surface is read-only (GetSourceAudioChannelMapping, GetAudioMapping, voice isolation). Verified via dir() + SetProperty docs AND by live mutating attempt on 21.0.0: SetProperty('Volume'|'Level'|'Gain'|'AudioVolume', 0) all return False (note 'Pan' is the VIDEO transform key, not audio pan, so it misleadingly succeeds). The gap is PER-PARAMETER control specifically: a whole saved mix CAN be applied wholesale via Project.ApplyFairlightPresetToCurrentTimeline(name), with the available names from Resolve.GetFairlightPresets() — so 'no Fairlight write path exists' would be too strong. Both methods require Resolve 20.2.2+; on 19.1.3 they are absent (confirmed live 2026-08-06), so on older builds the per-parameter gap really is the whole story.
|
|
135
135
|
- **Workaround / current handling:** To reapply a known mix, save it once as a Fairlight preset in the UI and apply it per-timeline with ApplyFairlightPresetToCurrentTimeline (exposed as resolve_control get_fairlight_presets + project_settings apply_fairlight_preset). Dial individual levels/pan/EQ/automation/FairlightFX in the Fairlight UI; beyond presets, only voice-isolation state and channel-mapping reads are scriptable.
|
|
136
136
|
- **Tags:** missing-method, audio, fairlight
|
|
137
137
|
|
|
@@ -182,7 +182,7 @@ equivalent, blocking full automation.
|
|
|
182
182
|
|
|
183
183
|
- **Object:** `TimelineItem / Timeline / Project`
|
|
184
184
|
- **Behavior:** There is no API method to set or query subtitle font family, font size, text color, background color, outline, shadow, position, alignment, or to apply/query subtitle style presets. TimelineItem.GetProperty() on subtitle items returns only transform/composite keys. Timeline.GetSetting() and Project.GetSetting() return None for all probed subtitle-style keys (e.g. 'subtitleFontName', 'subtitleFontSize', 'subtitleTextColor', 'subtitleBackgroundColor', 'subtitlePosition', 'subtitleAlignment', 'subtitlePreset', 'subtitleStyle'). Verified via dir(), GetProperty(), and GetSetting() on Resolve 21.0.0.48.
|
|
185
|
-
- **Workaround / current handling:** No API workaround exists, but the style IS reachable below the API: it lives in Sm2TiTrack.FieldsBlob for Type=2 tracks, as an EffectFiltersBA payload whose effect 136 carries a Qt QFont descriptor (param 18) and a normalised position vector (param 17). Exposed as project_db list_subtitle_styles / set_subtitle_style (font family/size/weight/italic + position). Confirmed live on
|
|
185
|
+
- **Workaround / current handling:** No API workaround exists, but the style IS reachable below the API: it lives in Sm2TiTrack.FieldsBlob for Type=2 tracks, as an EffectFiltersBA payload whose effect 136 carries a Qt QFont descriptor (param 18) and a normalised position vector (param 17). Exposed as project_db list_subtitle_styles / set_subtitle_style (font family/size/weight/italic + position). Confirmed live on Studio 19.1.3 (2026-08-06): Resolve opens a patched track and re-serialises it back to its own zstd form with the patched values intact, so it genuinely parses the write. Caveats: whole-TRACK style not per-caption, project must be CLOSED, Resolve must be fully quit and relaunched afterwards, and the track must already carry a style blob (a freshly added subtitle track has none until it is styled once in the UI). Burn-in overlays via Fusion titles remain a visual alternative but do not produce subtitle tracks.
|
|
186
186
|
- **Tags:** missing-method, subtitle, style, preset
|
|
187
187
|
|
|
188
188
|
### Speech recognition engine selection and SRT import
|
|
@@ -222,6 +222,15 @@ equivalent, blocking full automation.
|
|
|
222
222
|
- **Workaround / current handling:** SetCurrentFolder to the target bin first (media_pool set_current_folder), import, then restore the previous current folder if it matters.
|
|
223
223
|
- **Tags:** media-pool, import
|
|
224
224
|
|
|
225
|
+
### SetClipColor (undocumented value space; marker constants are a decoy)
|
|
226
|
+
|
|
227
|
+
- **Object:** `TimelineItem / MediaPoolItem`
|
|
228
|
+
- **Signature:** `(colorName) -> bool`
|
|
229
|
+
- **Behavior:** SetClipColor accepts exactly 16 names — the Edit-page clip-colour palette — and refuses everything else with a bare False, no exception and no other signal. Enumerated live on Studio 19.1.3.7 (2026-08-06) against both objects; the accepted set is IDENTICAL on TimelineItem and MediaPoolItem: Orange, Apricot, Yellow, Lime, Olive, Green, Teal, Navy, Blue, Purple, Violet, Pink, Tan, Beige, Brown, Chocolate. The empty string is refused too. The trap is that the scripting reference documents colorName as a bare string with no enumerated values, while exporting the MARKER palette as constants (resolve.MARKER_ROSE, resolve.MARKER_FUCHSIA, ...) — so the only colour vocabulary reachable from the API surface is the wrong one. Cyan, Red, Fuchsia, Rose, Lavender, Sky, Mint, Lemon, Sand, Cocoa and Cream are all marker-only and all refused. Five names overlap both palettes (Blue, Green, Yellow, Pink, Purple), which is why the decoy survives: reasoning from the marker constants scores 5 of 16 and looks like the right vocabulary with a few gaps.
|
|
230
|
+
- **Workaround / current handling:** Pass only the 16 clip-colour names; on False, treat it as an invalid name rather than an item/lock/page problem. The set is pinned in utils/clip_colors.py and named in the refusal remediation, but deliberately NOT enforced — it was measured on one build and a later Resolve could extend it.
|
|
231
|
+
- **Reference:** [issue #124](https://github.com/samuelgursky/davinci-resolve-mcp/issues/124)
|
|
232
|
+
- **Tags:** timeline-item, media-pool, silent-failure, undocumented-enum
|
|
233
|
+
|
|
225
234
|
### Project.SetCurrentRenderFormatAndCodec
|
|
226
235
|
|
|
227
236
|
- **Object:** `Project`
|
|
@@ -443,6 +452,15 @@ values, or automation-hostile modal prompts.
|
|
|
443
452
|
- **Reference:** [issue #59](https://github.com/samuelgursky/davinci-resolve-mcp/issues/59)
|
|
444
453
|
- **Tags:** render, deliver, silent-failure, id-vs-label
|
|
445
454
|
|
|
455
|
+
### TimelineItem.SetClipColor on generator/title items
|
|
456
|
+
|
|
457
|
+
- **Object:** `TimelineItem`
|
|
458
|
+
- **Signature:** `(colorName) -> bool`
|
|
459
|
+
- **Behavior:** On a generator or title item SetClipColor returns True with a VALID colour name and the colour does not persist — GetClipColor still reads '' immediately afterwards, and ClearClipColor changes nothing because there is nothing to clear. Measured on Studio 19.1.3.7 (2026-08-06) on a Solid Color generator. A media-backed item on the SAME timeline in the same session persists correctly (SetClipColor('Teal') -> True, GetClipColor -> 'Teal'), so the bool is honest for some items and a lie for others, with nothing in the return value to tell them apart. A timeline item's colour is also independent of its backing MediaPoolItem's: colouring the item leaves the pool clip at ''.
|
|
460
|
+
- **Workaround / current handling:** Never trust the bool alone — read GetClipColor back and compare. To mark a generator or title, use a timeline marker at its start instead; markers read back reliably.
|
|
461
|
+
- **Reference:** [issue #124](https://github.com/samuelgursky/davinci-resolve-mcp/issues/124)
|
|
462
|
+
- **Tags:** timeline-item, silent-failure, generator, readback-lies
|
|
463
|
+
|
|
446
464
|
### Project.SetRenderSettings (inherits the loaded preset)
|
|
447
465
|
|
|
448
466
|
- **Object:** `Project`
|
package/install.py
CHANGED
|
@@ -36,7 +36,7 @@ from src.utils.update_check import (
|
|
|
36
36
|
|
|
37
37
|
# ─── Version ──────────────────────────────────────────────────────────────────
|
|
38
38
|
|
|
39
|
-
VERSION = "2.82.
|
|
39
|
+
VERSION = "2.82.1"
|
|
40
40
|
# Only hard floor: mcp[cli] requires Python 3.10+. There is no upper bound —
|
|
41
41
|
# Resolve's scripting bridge loads into newer interpreters on recent builds
|
|
42
42
|
# (Python 3.14 verified against Resolve Studio 20.3.2). Older Resolve builds
|
package/package.json
CHANGED
|
@@ -167,7 +167,7 @@ function unwrapEffectFilters(raw) {
|
|
|
167
167
|
* *compressor* (the bundled fzstd is decompress-only); the cost is a larger
|
|
168
168
|
* blob, which does not matter here.
|
|
169
169
|
*
|
|
170
|
-
* Confirmed live against Resolve
|
|
170
|
+
* Confirmed live against Resolve Studio 19.1.3 (2026-08-06): a subtitle track patched with
|
|
171
171
|
* a 0x80 payload opens without error, and once Resolve next re-serialises that
|
|
172
172
|
* track it writes the style back out as 0x81 zstd with the font descriptor and
|
|
173
173
|
* position preserved exactly — i.e. Resolve genuinely parses the 0x80 form into
|
package/src/granular/common.py
CHANGED
|
@@ -85,7 +85,7 @@ if not logging.getLogger().handlers:
|
|
|
85
85
|
handlers=[logging.StreamHandler()],
|
|
86
86
|
)
|
|
87
87
|
|
|
88
|
-
VERSION = "2.82.
|
|
88
|
+
VERSION = "2.82.1"
|
|
89
89
|
logger = logging.getLogger("davinci-resolve-mcp")
|
|
90
90
|
logger.info(f"Starting DaVinci Resolve MCP Server v{VERSION}")
|
|
91
91
|
logger.info(f"Detected platform: {get_platform()}")
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Timeline item property, keyframe, and clip-level tools."""
|
|
2
2
|
|
|
3
3
|
from src.granular.common import * # noqa: F401,F403
|
|
4
|
+
from src.utils.clip_colors import clip_color_refusal
|
|
4
5
|
|
|
5
6
|
resolve = ResolveProxy()
|
|
6
7
|
|
|
@@ -1528,7 +1529,32 @@ def ti_set_clip_color(color: str, item_index: int = 0, track_type: str = "video"
|
|
|
1528
1529
|
item, err = _get_timeline_item(track_type, track_index, item_index)
|
|
1529
1530
|
if err:
|
|
1530
1531
|
return err
|
|
1531
|
-
|
|
1532
|
+
returned = bool(item.SetClipColor(color))
|
|
1533
|
+
readback = item.GetClipColor() or ""
|
|
1534
|
+
if not returned:
|
|
1535
|
+
refusal = clip_color_refusal(color)
|
|
1536
|
+
return {
|
|
1537
|
+
"success": False,
|
|
1538
|
+
"color": color,
|
|
1539
|
+
"error": f"SetClipColor refused '{color}'",
|
|
1540
|
+
"reason": refusal["reason"],
|
|
1541
|
+
"remediation": refusal["remediation"],
|
|
1542
|
+
"valid_colors": refusal["state"]["valid_colors"],
|
|
1543
|
+
}
|
|
1544
|
+
if readback != color:
|
|
1545
|
+
# Generator and title items take the call, return True, and drop the
|
|
1546
|
+
# colour (issue #124, Studio 19.1.3.7). Media-backed items persist.
|
|
1547
|
+
return {
|
|
1548
|
+
"success": False,
|
|
1549
|
+
"color": color,
|
|
1550
|
+
"readback": readback,
|
|
1551
|
+
"error": "SetClipColor returned True but the colour did not persist",
|
|
1552
|
+
"remediation": (
|
|
1553
|
+
"Generator and title items accept and discard clip colours; colour "
|
|
1554
|
+
"a media-backed item, or use a timeline marker instead."
|
|
1555
|
+
),
|
|
1556
|
+
}
|
|
1557
|
+
return {"success": True, "color": color, "readback": readback}
|
|
1532
1558
|
|
|
1533
1559
|
|
|
1534
1560
|
@mcp.tool()
|
package/src/server.py
CHANGED
|
@@ -11,7 +11,7 @@ Usage:
|
|
|
11
11
|
python src/server.py --full # Start the 341-tool granular server instead
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
|
-
VERSION = "2.82.
|
|
14
|
+
VERSION = "2.82.1"
|
|
15
15
|
|
|
16
16
|
import base64
|
|
17
17
|
import os
|
|
@@ -46,6 +46,7 @@ for p in [current_dir, project_dir]:
|
|
|
46
46
|
from src.utils.cdl import normalize_cdl_payload
|
|
47
47
|
from src.utils.mcp_stdio import run_fastmcp_stdio
|
|
48
48
|
from src.utils.api_truth import lookup_api_truth, VERIFIED_ON as _API_TRUTH_VERIFIED_ON
|
|
49
|
+
from src.utils import clip_colors as _clip_colors
|
|
49
50
|
from src.utils.contracts import validate as _validate_params
|
|
50
51
|
from src.utils.cut_ir import build_cut_list as _build_cut_list
|
|
51
52
|
from src.utils.page_lock import (
|
|
@@ -9006,6 +9007,66 @@ def _safe_int(value: Any, default: int, *, minimum: Optional[int] = None, maximu
|
|
|
9006
9007
|
return parsed
|
|
9007
9008
|
|
|
9008
9009
|
|
|
9010
|
+
def _set_clip_color_checked(obj, color, *, kind: str):
|
|
9011
|
+
"""SetClipColor, with the bare bool turned into something a caller can act on.
|
|
9012
|
+
|
|
9013
|
+
Two measured failures hide behind that bool (issue #124, enumerated live on
|
|
9014
|
+
Studio 19.1.3.7):
|
|
9015
|
+
|
|
9016
|
+
- A name outside the 16-name Edit-page palette is refused with `False` and
|
|
9017
|
+
nothing else. The marker constants are the only colour vocabulary the
|
|
9018
|
+
scripting reference enumerates, so they are what an agent reaches for, and
|
|
9019
|
+
most of them are refused.
|
|
9020
|
+
- On generator and title items the call returns `True` and the colour does
|
|
9021
|
+
not persist — `GetClipColor` still reads empty afterwards. Media-backed
|
|
9022
|
+
items on the same timeline persist correctly, so the bool is honest for
|
|
9023
|
+
some items and a lie for others.
|
|
9024
|
+
|
|
9025
|
+
So the return value is never trusted on its own: the colour is read back and
|
|
9026
|
+
the response reports what actually stuck.
|
|
9027
|
+
"""
|
|
9028
|
+
returned = bool(obj.SetClipColor(color))
|
|
9029
|
+
readback = None
|
|
9030
|
+
if _has_method(obj, "GetClipColor"):
|
|
9031
|
+
try:
|
|
9032
|
+
readback = obj.GetClipColor()
|
|
9033
|
+
except Exception: # pragma: no cover - defensive; getter is documented
|
|
9034
|
+
readback = None
|
|
9035
|
+
persisted = (readback == color) if readback is not None else None
|
|
9036
|
+
|
|
9037
|
+
if not returned:
|
|
9038
|
+
refusal = _clip_colors.clip_color_refusal(color)
|
|
9039
|
+
return _err(
|
|
9040
|
+
f"SetClipColor refused '{color}' on this {kind}",
|
|
9041
|
+
code="CLIP_COLOR_REJECTED",
|
|
9042
|
+
category="invalid_input",
|
|
9043
|
+
reason=refusal["reason"],
|
|
9044
|
+
remediation=refusal["remediation"],
|
|
9045
|
+
state=refusal["state"],
|
|
9046
|
+
)
|
|
9047
|
+
if persisted is False:
|
|
9048
|
+
# The generator/title case. Reporting success here is the silent lie.
|
|
9049
|
+
return {
|
|
9050
|
+
"success": False,
|
|
9051
|
+
"color": color,
|
|
9052
|
+
"readback": readback,
|
|
9053
|
+
"warnings": [{
|
|
9054
|
+
"code": "CLIP_COLOR_NOT_PERSISTED",
|
|
9055
|
+
"message": (
|
|
9056
|
+
f"SetClipColor returned True on this {kind} but the colour did "
|
|
9057
|
+
f"not persist — GetClipColor reads {readback!r}. Measured on "
|
|
9058
|
+
"generator and title items, which take the call and drop it; "
|
|
9059
|
+
"media-backed items on the same timeline persist correctly."
|
|
9060
|
+
),
|
|
9061
|
+
"remediation": (
|
|
9062
|
+
"Colour a media-backed item, or mark the generator another way "
|
|
9063
|
+
"(a timeline marker at its start reads back reliably)."
|
|
9064
|
+
),
|
|
9065
|
+
}],
|
|
9066
|
+
}
|
|
9067
|
+
return {"success": True, "color": color, "readback": readback}
|
|
9068
|
+
|
|
9069
|
+
|
|
9009
9070
|
def _filter_to_keys(settings: Any, allowed) -> Tuple[Dict[str, Any], list]:
|
|
9010
9071
|
"""Whitelist a settings/options dict to the keys a Resolve API documents.
|
|
9011
9072
|
|
|
@@ -17783,7 +17844,7 @@ def media_pool_item(action: str, params: Optional[Dict[str, Any]] = None) -> Dic
|
|
|
17783
17844
|
elif action == "get_clip_color":
|
|
17784
17845
|
return {"color": clip.GetClipColor()}
|
|
17785
17846
|
elif action == "set_clip_color":
|
|
17786
|
-
return
|
|
17847
|
+
return _set_clip_color_checked(clip, p["color"], kind="media pool item")
|
|
17787
17848
|
elif action == "clear_clip_color":
|
|
17788
17849
|
return {"success": bool(clip.ClearClipColor())}
|
|
17789
17850
|
elif action == "link_proxy":
|
|
@@ -22251,7 +22312,7 @@ def timeline_item_markers(action: str, params: Optional[Dict[str, Any]] = None)
|
|
|
22251
22312
|
elif action == "get_clip_color":
|
|
22252
22313
|
return {"color": item.GetClipColor()}
|
|
22253
22314
|
elif action == "set_clip_color":
|
|
22254
|
-
return
|
|
22315
|
+
return _set_clip_color_checked(item, p["color"], kind="timeline item")
|
|
22255
22316
|
elif action == "clear_clip_color":
|
|
22256
22317
|
return {"success": bool(item.ClearClipColor())}
|
|
22257
22318
|
return _unknown(action, ["add","get_all","get_by_custom_data","update_custom_data","get_custom_data","delete_by_color","delete_at_frame","delete_by_custom_data","add_flag","get_flags","clear_flags","get_clip_color","set_clip_color","clear_clip_color"])
|
package/src/utils/api_truth.py
CHANGED
|
@@ -863,7 +863,10 @@ API_TRUTH: List[Dict[str, Any]] = [
|
|
|
863
863
|
"mix CAN be applied wholesale via "
|
|
864
864
|
"Project.ApplyFairlightPresetToCurrentTimeline(name), with the "
|
|
865
865
|
"available names from Resolve.GetFairlightPresets() — so "
|
|
866
|
-
"'no Fairlight write path exists' would be too strong."
|
|
866
|
+
"'no Fairlight write path exists' would be too strong. Both "
|
|
867
|
+
"methods require Resolve 20.2.2+; on 19.1.3 they are absent "
|
|
868
|
+
"(confirmed live 2026-08-06), so on older builds the "
|
|
869
|
+
"per-parameter gap really is the whole story.",
|
|
867
870
|
"recommended": "To reapply a known mix, save it once as a Fairlight preset "
|
|
868
871
|
"in the UI and apply it per-timeline with "
|
|
869
872
|
"ApplyFairlightPresetToCurrentTimeline (exposed as "
|
|
@@ -1005,7 +1008,7 @@ API_TRUTH: List[Dict[str, Any]] = [
|
|
|
1005
1008
|
"a normalised position vector (param 17). Exposed as "
|
|
1006
1009
|
"project_db list_subtitle_styles / set_subtitle_style "
|
|
1007
1010
|
"(font family/size/weight/italic + position). Confirmed "
|
|
1008
|
-
"live on
|
|
1011
|
+
"live on Studio 19.1.3 (2026-08-06): Resolve opens a patched track "
|
|
1009
1012
|
"and re-serialises it back to its own zstd form with the "
|
|
1010
1013
|
"patched values intact, so it genuinely parses the write. "
|
|
1011
1014
|
"Caveats: whole-TRACK style not per-caption, project must "
|
|
@@ -1421,6 +1424,60 @@ API_TRUTH: List[Dict[str, Any]] = [
|
|
|
1421
1424
|
"issue": 59,
|
|
1422
1425
|
"mitigation": ["_render_format_id", "_render_codec_id"],
|
|
1423
1426
|
},
|
|
1427
|
+
{
|
|
1428
|
+
"symbol": "SetClipColor (undocumented value space; marker constants are a decoy)",
|
|
1429
|
+
"object": "TimelineItem / MediaPoolItem",
|
|
1430
|
+
"signature": "(colorName) -> bool",
|
|
1431
|
+
"reality": "SetClipColor accepts exactly 16 names — the Edit-page clip-colour "
|
|
1432
|
+
"palette — and refuses everything else with a bare False, no "
|
|
1433
|
+
"exception and no other signal. Enumerated live on Studio "
|
|
1434
|
+
"19.1.3.7 (2026-08-06) against both objects; the accepted set is "
|
|
1435
|
+
"IDENTICAL on TimelineItem and MediaPoolItem: Orange, Apricot, "
|
|
1436
|
+
"Yellow, Lime, Olive, Green, Teal, Navy, Blue, Purple, Violet, "
|
|
1437
|
+
"Pink, Tan, Beige, Brown, Chocolate. The empty string is refused "
|
|
1438
|
+
"too. The trap is that the scripting reference documents "
|
|
1439
|
+
"colorName as a bare string with no enumerated values, while "
|
|
1440
|
+
"exporting the MARKER palette as constants (resolve.MARKER_ROSE, "
|
|
1441
|
+
"resolve.MARKER_FUCHSIA, ...) — so the only colour vocabulary "
|
|
1442
|
+
"reachable from the API surface is the wrong one. Cyan, Red, "
|
|
1443
|
+
"Fuchsia, Rose, Lavender, Sky, Mint, Lemon, Sand, Cocoa and Cream "
|
|
1444
|
+
"are all marker-only and all refused. Five names overlap both "
|
|
1445
|
+
"palettes (Blue, Green, Yellow, Pink, Purple), which is why the "
|
|
1446
|
+
"decoy survives: reasoning from the marker constants scores 5 of "
|
|
1447
|
+
"16 and looks like the right vocabulary with a few gaps.",
|
|
1448
|
+
"recommended": "Pass only the 16 clip-colour names; on False, treat it as an "
|
|
1449
|
+
"invalid name rather than an item/lock/page problem. The set "
|
|
1450
|
+
"is pinned in utils/clip_colors.py and named in the refusal "
|
|
1451
|
+
"remediation, but deliberately NOT enforced — it was measured "
|
|
1452
|
+
"on one build and a later Resolve could extend it.",
|
|
1453
|
+
"tags": ["timeline-item", "media-pool", "silent-failure", "undocumented-enum"],
|
|
1454
|
+
"submit": "missing",
|
|
1455
|
+
"issue": 124,
|
|
1456
|
+
"mitigation": ["_set_clip_color_checked", "ti_set_clip_color"],
|
|
1457
|
+
},
|
|
1458
|
+
{
|
|
1459
|
+
"symbol": "TimelineItem.SetClipColor on generator/title items",
|
|
1460
|
+
"object": "TimelineItem",
|
|
1461
|
+
"signature": "(colorName) -> bool",
|
|
1462
|
+
"reality": "On a generator or title item SetClipColor returns True with a "
|
|
1463
|
+
"VALID colour name and the colour does not persist — GetClipColor "
|
|
1464
|
+
"still reads '' immediately afterwards, and ClearClipColor "
|
|
1465
|
+
"changes nothing because there is nothing to clear. Measured on "
|
|
1466
|
+
"Studio 19.1.3.7 (2026-08-06) on a Solid Color generator. A "
|
|
1467
|
+
"media-backed item on the SAME timeline in the same session "
|
|
1468
|
+
"persists correctly (SetClipColor('Teal') -> True, GetClipColor "
|
|
1469
|
+
"-> 'Teal'), so the bool is honest for some items and a lie for "
|
|
1470
|
+
"others, with nothing in the return value to tell them apart. A "
|
|
1471
|
+
"timeline item's colour is also independent of its backing "
|
|
1472
|
+
"MediaPoolItem's: colouring the item leaves the pool clip at ''.",
|
|
1473
|
+
"recommended": "Never trust the bool alone — read GetClipColor back and "
|
|
1474
|
+
"compare. To mark a generator or title, use a timeline marker "
|
|
1475
|
+
"at its start instead; markers read back reliably.",
|
|
1476
|
+
"tags": ["timeline-item", "silent-failure", "generator", "readback-lies"],
|
|
1477
|
+
"submit": "bug",
|
|
1478
|
+
"issue": 124,
|
|
1479
|
+
"mitigation": ["_set_clip_color_checked", "ti_set_clip_color"],
|
|
1480
|
+
},
|
|
1424
1481
|
{
|
|
1425
1482
|
"symbol": "Project.SetRenderSettings (inherits the loaded preset)",
|
|
1426
1483
|
"object": "Project",
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""The value space of SetClipColor, and an honest refusal when a name misses.
|
|
2
|
+
|
|
3
|
+
`SetClipColor(colorName) --> Bool` is documented with `colorName` as a bare
|
|
4
|
+
string and no enumerated values anywhere in the scripting reference. The same
|
|
5
|
+
reference *does* export a discoverable colour vocabulary — the marker constants
|
|
6
|
+
(`resolve.MARKER_ROSE`, `resolve.MARKER_FUCHSIA`, …) — so the only colour names
|
|
7
|
+
reachable from the API surface come from the marker palette, and those are
|
|
8
|
+
largely the ones `SetClipColor` refuses. Issue #124 named this a decoy
|
|
9
|
+
vocabulary, which is exactly right.
|
|
10
|
+
|
|
11
|
+
Enumerated live on **DaVinci Resolve Studio 19.1.3.7 (macOS, 2026-08-06)**
|
|
12
|
+
against both objects that expose the method. Both accept the same 16 names —
|
|
13
|
+
the Edit-page clip-colour palette — and refuse everything else with a bare
|
|
14
|
+
`False`, including every marker-only name and the empty string.
|
|
15
|
+
|
|
16
|
+
The five names that overlap the two palettes (Blue, Green, Yellow, Pink,
|
|
17
|
+
Purple) are what makes this trap so durable: an agent reasoning from the marker
|
|
18
|
+
constants gets five hits out of sixteen and concludes the vocabulary is right.
|
|
19
|
+
|
|
20
|
+
This set is deliberately NOT enforced. It was measured on one build, and a
|
|
21
|
+
future Resolve could extend it; hard-rejecting an unlisted name would turn a
|
|
22
|
+
working call into a failure on a build we have not seen. Callers pass the name
|
|
23
|
+
through, and when Resolve refuses, the remediation names the measured-valid set.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
VERIFIED_ON = "DaVinci Resolve Studio 19.1.3.7 (macOS, 2026-08-06)"
|
|
27
|
+
|
|
28
|
+
# Accepted by BOTH TimelineItem.SetClipColor and MediaPoolItem.SetClipColor.
|
|
29
|
+
CLIP_COLORS = (
|
|
30
|
+
"Orange", "Apricot", "Yellow", "Lime", "Olive", "Green", "Teal", "Navy",
|
|
31
|
+
"Blue", "Purple", "Violet", "Pink", "Tan", "Beige", "Brown", "Chocolate",
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# Measured refused. Kept explicit because these are the names an agent actually
|
|
35
|
+
# reaches for: every one is either a marker constant or a common colour word.
|
|
36
|
+
REFUSED_NAMES = (
|
|
37
|
+
"Cyan", "Red", "Fuchsia", "Rose", "Lavender", "Sky", "Mint", "Lemon",
|
|
38
|
+
"Sand", "Cocoa", "Cream", "Apple", "Magenta", "White", "Black", "Gray",
|
|
39
|
+
"Grey", "",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# The marker-palette names that are NOT clip colours — the decoy set proper.
|
|
43
|
+
MARKER_ONLY_NAMES = (
|
|
44
|
+
"Cyan", "Red", "Fuchsia", "Rose", "Lavender", "Sky", "Mint", "Lemon",
|
|
45
|
+
"Sand", "Cocoa", "Cream",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def is_known_clip_color(color) -> bool:
|
|
50
|
+
"""True if `color` is in the measured-valid set. Case-sensitive, as Resolve is."""
|
|
51
|
+
return isinstance(color, str) and color in CLIP_COLORS
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def clip_color_refusal(color) -> dict:
|
|
55
|
+
"""Error payload for a SetClipColor that came back False.
|
|
56
|
+
|
|
57
|
+
Separates 'you used a marker name' from 'Resolve refused for another
|
|
58
|
+
reason', because the caller cannot tell them apart from the bare bool.
|
|
59
|
+
"""
|
|
60
|
+
marker_decoy = isinstance(color, str) and color in MARKER_ONLY_NAMES
|
|
61
|
+
if marker_decoy:
|
|
62
|
+
reason = (
|
|
63
|
+
f"'{color}' is a MARKER colour, not a clip colour. The scripting "
|
|
64
|
+
"reference enumerates the marker constants and nothing else, so it "
|
|
65
|
+
"is the vocabulary an agent finds first — and SetClipColor refuses "
|
|
66
|
+
"most of it with a bare False."
|
|
67
|
+
)
|
|
68
|
+
elif is_known_clip_color(color):
|
|
69
|
+
reason = (
|
|
70
|
+
f"'{color}' is a valid clip colour on {VERIFIED_ON}, so this refusal "
|
|
71
|
+
"is about the item or the session, not the name. Note that "
|
|
72
|
+
"SetClipColor returns True WITHOUT persisting on generator and title "
|
|
73
|
+
"items — check GetClipColor rather than the bool."
|
|
74
|
+
)
|
|
75
|
+
else:
|
|
76
|
+
reason = (
|
|
77
|
+
f"'{color}' is not in the clip-colour palette. SetClipColor refuses "
|
|
78
|
+
"undocumented names with a bare False and no other signal."
|
|
79
|
+
)
|
|
80
|
+
return {
|
|
81
|
+
"reason": reason,
|
|
82
|
+
"remediation": (
|
|
83
|
+
"Use one of the 16 Edit-page clip colours: "
|
|
84
|
+
+ ", ".join(CLIP_COLORS)
|
|
85
|
+
+ f". Measured on {VERIFIED_ON}; the set is not enforced here because "
|
|
86
|
+
"a later build could extend it."
|
|
87
|
+
),
|
|
88
|
+
"state": {
|
|
89
|
+
"requested_color": color,
|
|
90
|
+
"valid_colors": list(CLIP_COLORS),
|
|
91
|
+
"is_marker_only_name": marker_decoy,
|
|
92
|
+
"verified_on": VERIFIED_ON,
|
|
93
|
+
},
|
|
94
|
+
}
|