davinci-resolve-mcp 4.8.0 → 4.8.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 +35 -0
- package/README.md +1 -1
- package/README.zh-CN.md +2 -2
- package/docs/SKILL.md +7 -1
- package/docs/reference/readwrite-symmetry.md +3 -3
- package/install.py +1 -1
- package/package.json +1 -1
- package/src/granular/common.py +1 -1
- package/src/server.py +108 -6
- package/src/utils/api_truth.py +27 -0
- package/src/utils/destructive_hook.py +1 -1
- package/src/utils/execution_lifecycle.py +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,41 @@
|
|
|
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 v4.8.1 — text modifiers (Follower) attach to TextPlus inputs
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **`fusion_comp add_modifier(tool_name, input_name, modifier)`** — attach any modifier
|
|
10
|
+
to a Fusion input and get back the tool Fusion created for it
|
|
11
|
+
(`modifier_tool`, `modifier_type`), so a TEXT modifier can then be driven with
|
|
12
|
+
`set_input` / `add_keyframe` on that tool. ([#250](https://github.com/samuelgursky/davinci-resolve-mcp/issues/250), reported by @artpavelalex-ux — the first report filed through this server's own `report_issue` action)
|
|
13
|
+
The reporter wanted a Follower on a TextPlus `StyledText` for per-character
|
|
14
|
+
typewriter animation, and `add_keyframe(modifier="Follower")` failed with
|
|
15
|
+
`FUSION_ADD_MODIFIER_FAILED` because Fusion rejects the attach. **Measured on
|
|
16
|
+
Studio 19.1.3.7:** `Tool.AddModifier` wants the modifier's REGISTRY ID, not its
|
|
17
|
+
display name — `AddModifier("StyledText", "Follower")` and `"TextFollower"` return
|
|
18
|
+
False and attach nothing; `"StyledTextFollower"` returns True, creates a `Follower1`
|
|
19
|
+
tool of that ID and connects it to the input. The mapping now lives in
|
|
20
|
+
`_FUSION_MODIFIER_IDS` and both `add_modifier` and `add_keyframe`'s `modifier`
|
|
21
|
+
parameter use it, so `Follower` works as written. Attachment is verified by
|
|
22
|
+
readback (the input's connected output), never by AddModifier's bool, which is
|
|
23
|
+
unreliable through the Lua bridge. An input that already has a modifier is refused
|
|
24
|
+
with `FUSION_INPUT_ALREADY_CONNECTED` and the existing tool named; a modifier
|
|
25
|
+
Fusion rejects is reported with the registry-ID remediation rather than claimed.
|
|
26
|
+
- Rated a LOW bounded reversible edit and registered in the destructive hook, so it
|
|
27
|
+
gets safe-mode, an audit row, and the timeline archive copy before the edit like
|
|
28
|
+
every other compound write; `add_keyframe` itself stays on the ratchet backlog.
|
|
29
|
+
- New `api_truth` entry `Tool.AddModifier` (`verified_on` 19.1.3.7) mapped through
|
|
30
|
+
`ACTION_SYMBOLS`, so `add_modifier` results carry the fact as a `known_limitation`.
|
|
31
|
+
- **Live-validated on landing through the real action** on a disposable timeline:
|
|
32
|
+
`add_modifier(Follower)` → `Follower1` / `StyledTextFollower`; `set_input(Delay=5)`
|
|
33
|
+
on the returned tool read back `5.0`; a second attach → `FUSION_INPUT_ALREADY_CONNECTED`
|
|
34
|
+
naming `Follower1`; a bogus modifier → `FUSION_ADD_MODIFIER_FAILED`. Unit tests in
|
|
35
|
+
`tests/test_fusion_add_modifier.py` against fakes that accept only the registry ID.
|
|
36
|
+
- Not measured: other text modifiers (the mapping table holds only the Follower),
|
|
37
|
+
builds other than 19.1.3.7, and whether the per-character transforms the reporter
|
|
38
|
+
wants animate as expected once driven — that is theirs to confirm.
|
|
39
|
+
|
|
5
40
|
## What's New in v4.8.0 — one read before planning: `project_manager snapshot`
|
|
6
41
|
|
|
7
42
|
### Added
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
English | [简体中文](README.zh-CN.md)
|
|
4
4
|
|
|
5
|
-
[](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
|
|
6
6
|
[](https://www.npmjs.com/package/davinci-resolve-mcp)
|
|
7
7
|
[](docs/reference/api-coverage.md)
|
|
8
8
|
[-blue.svg)](#server-modes)
|
package/README.zh-CN.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[English](README.md) | 简体中文
|
|
4
4
|
|
|
5
|
-
[](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
|
|
6
6
|
[](https://www.npmjs.com/package/davinci-resolve-mcp)
|
|
7
7
|
[](docs/reference/api-coverage.md)
|
|
8
8
|
[-blue.svg)](#服务器模式)
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
[](https://www.python.org/downloads/)
|
|
13
13
|
[](https://opensource.org/licenses/MIT)
|
|
14
14
|
|
|
15
|
-
> 本翻译对应 v4.8.
|
|
15
|
+
> 本翻译对应 v4.8.1 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
|
|
16
16
|
|
|
17
17
|
一个 Model Context Protocol (MCP) 服务器,让 AI 助手通过官方脚本 API 控制 DaVinci Resolve Studio(达芬奇)。它提供完整的 API 覆盖,外加带护栏的工作流助手,涵盖剪辑、媒体池整理、渲染设置、审阅标记、调色、Fusion、Fairlight、项目生命周期任务、扩展开发,以及不碰源媒体的媒体分析。
|
|
18
18
|
|
package/docs/SKILL.md
CHANGED
|
@@ -2038,7 +2038,13 @@ Key actions:
|
|
|
2038
2038
|
`get_input(tool_name, input_name, time?)`
|
|
2039
2039
|
- `get_inputs(tool_name)` / `get_outputs(tool_name)`
|
|
2040
2040
|
- `set_attrs(tool_name, attrs)` / `get_attrs(tool_name)`
|
|
2041
|
-
- `add_keyframe(tool_name, input_name, time, value)`
|
|
2041
|
+
- `add_keyframe(tool_name, input_name, time, value, modifier?)` — attaches a
|
|
2042
|
+
BezierSpline (or `modifier`, e.g. `Path` for Point inputs) on first use
|
|
2043
|
+
- `add_modifier(tool_name, input_name, modifier)` → `{modifier_tool, modifier_type}`
|
|
2044
|
+
— attach any modifier and get back the tool Fusion created, so a text modifier
|
|
2045
|
+
(`Follower` on a TextPlus `StyledText`) can be driven with `set_input` /
|
|
2046
|
+
`add_keyframe` on that tool (e.g. `Delay`). Fusion wants the registry ID
|
|
2047
|
+
(`StyledTextFollower`, measured on Studio 19.1.3.7); `Follower` is mapped for you
|
|
2042
2048
|
- `get_position(tool_name)` / `set_position(tool_name, x, y)` — read/write a node's
|
|
2043
2049
|
position on the FlowView canvas; `set_position` returns a position read-back
|
|
2044
2050
|
- `copy_tool(tool_name, name?, x?, y?)` — duplicate a node (settings copied via a
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Read/Write Symmetry Audit
|
|
4
4
|
|
|
5
|
-
- write-style action occurrences scanned: **
|
|
5
|
+
- write-style action occurrences scanned: **125**
|
|
6
6
|
- write-style action occurrences with a matching read: **76**
|
|
7
7
|
- distinct high-signal `set_` actions without a direct/known readback: **4**
|
|
8
8
|
|
|
@@ -13,6 +13,6 @@
|
|
|
13
13
|
- `set_keyframe_interpolation`
|
|
14
14
|
- `set_node_enabled`
|
|
15
15
|
|
|
16
|
-
## Low-signal (create/add/insert/apply/import — usually expected):
|
|
16
|
+
## Low-signal (create/add/insert/apply/import — usually expected): 44 distinct names
|
|
17
17
|
|
|
18
|
-
`add_clip_mattes`, `add_comp`, `add_fusion_mask`, `add_subfolder`, `add_sync_event_markers`, `add_timeline_mattes`, `add_track`, `add_transition`, `add_version`, `apply_arri_cdl_lut`, `apply_cuts`, `apply_fairlight_preset`, `apply_grade_from_drx`, `apply_look_to_items`, `apply_spec`, `apply_trace_plan`, `create_compound_clip`, `create_fusion_clip`, `create_magic_mask`, `create_multicam_clip`, `create_stereo_clip`, `create_subtitles`, `create_timeline`, `create_timeline_from_clips`, `create_variant_from_ranges`, `import_comp`, `import_folder`, `import_from_drp`, `import_into_timeline`, `import_media`, `import_preset`, `import_project`, `import_render`, `import_timeline`, `import_timeline_checked`, `import_to_pool`, `insert_audio`, `insert_fusion_composition`, `insert_fusion_generator`, `insert_fusion_title`, `insert_generator`, `insert_ofx_generator`, `insert_title`
|
|
18
|
+
`add_clip_mattes`, `add_comp`, `add_fusion_mask`, `add_modifier`, `add_subfolder`, `add_sync_event_markers`, `add_timeline_mattes`, `add_track`, `add_transition`, `add_version`, `apply_arri_cdl_lut`, `apply_cuts`, `apply_fairlight_preset`, `apply_grade_from_drx`, `apply_look_to_items`, `apply_spec`, `apply_trace_plan`, `create_compound_clip`, `create_fusion_clip`, `create_magic_mask`, `create_multicam_clip`, `create_stereo_clip`, `create_subtitles`, `create_timeline`, `create_timeline_from_clips`, `create_variant_from_ranges`, `import_comp`, `import_folder`, `import_from_drp`, `import_into_timeline`, `import_media`, `import_preset`, `import_project`, `import_render`, `import_timeline`, `import_timeline_checked`, `import_to_pool`, `insert_audio`, `insert_fusion_composition`, `insert_fusion_generator`, `insert_fusion_title`, `insert_generator`, `insert_ofx_generator`, `insert_title`
|
package/install.py
CHANGED
|
@@ -37,7 +37,7 @@ from src.utils.update_check import (
|
|
|
37
37
|
|
|
38
38
|
# ─── Version ──────────────────────────────────────────────────────────────────
|
|
39
39
|
|
|
40
|
-
VERSION = "4.8.
|
|
40
|
+
VERSION = "4.8.1"
|
|
41
41
|
# Only hard floor: mcp[cli] requires Python 3.10+. There is no upper bound —
|
|
42
42
|
# Resolve's scripting bridge loads into newer interpreters on recent builds
|
|
43
43
|
# (Python 3.14 verified against Resolve Studio 20.3.2). Older Resolve builds
|
package/package.json
CHANGED
package/src/granular/common.py
CHANGED
|
@@ -93,7 +93,7 @@ if not logging.getLogger().handlers:
|
|
|
93
93
|
handlers=[logging.StreamHandler()],
|
|
94
94
|
)
|
|
95
95
|
|
|
96
|
-
VERSION = "4.8.
|
|
96
|
+
VERSION = "4.8.1"
|
|
97
97
|
logger = logging.getLogger("davinci-resolve-mcp")
|
|
98
98
|
logger.info(f"Starting DaVinci Resolve MCP Server v{VERSION}")
|
|
99
99
|
logger.info(f"Detected platform: {get_platform()}")
|
package/src/server.py
CHANGED
|
@@ -11,7 +11,7 @@ Usage:
|
|
|
11
11
|
python src/server.py --full # Start the 377-tool granular server instead
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
|
-
VERSION = "4.8.
|
|
14
|
+
VERSION = "4.8.1"
|
|
15
15
|
|
|
16
16
|
import base64
|
|
17
17
|
import os
|
|
@@ -30412,6 +30412,25 @@ def _fusion_keyframe_frames(inp) -> List[float]:
|
|
|
30412
30412
|
return sorted(float(frame) for frame in kfs.values())
|
|
30413
30413
|
|
|
30414
30414
|
|
|
30415
|
+
#: Modifier names callers use -> the registry ID `Tool.AddModifier` wants.
|
|
30416
|
+
#: Measured on Studio 19.1.3.7 (issue #250) on a TextPlus StyledText input:
|
|
30417
|
+
#: AddModifier("StyledText", "Follower") and "TextFollower" return False and
|
|
30418
|
+
#: attach nothing; "StyledTextFollower" returns True, creates a `Follower1` tool
|
|
30419
|
+
#: of that ID and connects it to the input. Spline modifiers already go by their
|
|
30420
|
+
#: registry ID (BezierSpline, Path). Keys are compared case-insensitively.
|
|
30421
|
+
_FUSION_MODIFIER_IDS = {
|
|
30422
|
+
"follower": "StyledTextFollower",
|
|
30423
|
+
"textfollower": "StyledTextFollower",
|
|
30424
|
+
"styledtextfollower": "StyledTextFollower",
|
|
30425
|
+
}
|
|
30426
|
+
|
|
30427
|
+
|
|
30428
|
+
def _fusion_modifier_id(name: Any) -> str:
|
|
30429
|
+
"""The registry ID for a modifier a caller named; unknown names pass through."""
|
|
30430
|
+
text = str(name or "").strip()
|
|
30431
|
+
return _FUSION_MODIFIER_IDS.get(text.lower(), text)
|
|
30432
|
+
|
|
30433
|
+
|
|
30415
30434
|
def _fusion_input_spline(inp):
|
|
30416
30435
|
"""The modifier/spline tool driving `inp`, or None when it is not animated.
|
|
30417
30436
|
|
|
@@ -30565,7 +30584,17 @@ def fusion_comp(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[st
|
|
|
30565
30584
|
get_input(tool_name, input_name, time?) -> {value}
|
|
30566
30585
|
set_attrs(tool_name, attrs) -> {success}
|
|
30567
30586
|
get_attrs(tool_name) -> {attrs}
|
|
30568
|
-
add_keyframe(tool_name, input_name, time, value) -> {success}
|
|
30587
|
+
add_keyframe(tool_name, input_name, time, value, modifier?) -> {success}
|
|
30588
|
+
Attaches a BezierSpline (or `modifier`, e.g. 'Path' for Point inputs)
|
|
30589
|
+
the first time an input is animated. Modifier names are mapped to
|
|
30590
|
+
their registry ID ('Follower' -> 'StyledTextFollower').
|
|
30591
|
+
add_modifier(tool_name, input_name, modifier) -> {success, modifier_tool, modifier_type}
|
|
30592
|
+
Attach any modifier and return the tool Fusion created for it, so a
|
|
30593
|
+
TEXT modifier (Follower on a TextPlus StyledText) can then be driven
|
|
30594
|
+
with set_input / add_keyframe on that tool (e.g. Delay). Refuses an
|
|
30595
|
+
input that already has a modifier. Measured on Studio 19.1.3.7:
|
|
30596
|
+
Fusion wants the registry ID ('StyledTextFollower'); 'Follower' is
|
|
30597
|
+
mapped for you.
|
|
30569
30598
|
get_keyframes(tool_name, input_name) -> {keyframes}
|
|
30570
30599
|
delete_keyframe(tool_name, input_name, time) -> {success, time, remaining_keyframes}
|
|
30571
30600
|
Deletes on the spline attached to the input. Structured errors when the
|
|
@@ -30844,7 +30873,8 @@ def fusion_comp(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[st
|
|
|
30844
30873
|
# input still has no connected output, the assignment on the next
|
|
30845
30874
|
# line would set a STATIC value and _ok() would report a keyframe
|
|
30846
30875
|
# that does not exist.
|
|
30847
|
-
|
|
30876
|
+
_modifier_id = _fusion_modifier_id(p.get("modifier", "BezierSpline"))
|
|
30877
|
+
tool.AddModifier(p["input_name"], _modifier_id)
|
|
30848
30878
|
try:
|
|
30849
30879
|
attached = tool[p["input_name"]].GetConnectedOutput() is not None
|
|
30850
30880
|
except Exception:
|
|
@@ -30860,14 +30890,86 @@ def fusion_comp(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[st
|
|
|
30860
30890
|
"Reporting success here would claim a keyframe that "
|
|
30861
30891
|
"does not exist.",
|
|
30862
30892
|
remediation="Check the input accepts the modifier type "
|
|
30863
|
-
f"({
|
|
30864
|
-
"
|
|
30893
|
+
f"({_modifier_id!r}); Point inputs take 'Path'. "
|
|
30894
|
+
"A TEXT modifier (Follower) is not a spline: "
|
|
30895
|
+
"attach it with add_modifier, then drive the "
|
|
30896
|
+
"modifier tool it returns.",
|
|
30865
30897
|
)
|
|
30866
30898
|
tool[p["input_name"]][p["time"]] = p["value"]
|
|
30867
30899
|
return _ok()
|
|
30868
30900
|
finally:
|
|
30869
30901
|
comp.EndUndo(True)
|
|
30870
30902
|
|
|
30903
|
+
elif action == "add_modifier":
|
|
30904
|
+
tool = comp.FindTool(p["tool_name"])
|
|
30905
|
+
if not tool:
|
|
30906
|
+
return _err(f"Tool '{p['tool_name']}' not found")
|
|
30907
|
+
input_name = p.get("input_name")
|
|
30908
|
+
requested = p.get("modifier")
|
|
30909
|
+
if not input_name or not requested:
|
|
30910
|
+
return _err("add_modifier requires input_name and modifier",
|
|
30911
|
+
category="invalid_input")
|
|
30912
|
+
inp = tool[input_name]
|
|
30913
|
+
if not inp:
|
|
30914
|
+
return _err(f"Input '{input_name}' not found on tool '{p['tool_name']}'")
|
|
30915
|
+
modifier_id = _fusion_modifier_id(requested)
|
|
30916
|
+
try:
|
|
30917
|
+
existing = inp.GetConnectedOutput()
|
|
30918
|
+
except Exception:
|
|
30919
|
+
existing = None
|
|
30920
|
+
if existing is not None:
|
|
30921
|
+
existing_tool = existing.GetTool()
|
|
30922
|
+
attrs = (existing_tool.GetAttrs() or {}) if existing_tool else {}
|
|
30923
|
+
return _err(
|
|
30924
|
+
f"'{p['tool_name']}.{input_name}' already has a modifier attached.",
|
|
30925
|
+
code="FUSION_INPUT_ALREADY_CONNECTED", category="precondition",
|
|
30926
|
+
retryable=False,
|
|
30927
|
+
state={"modifier_tool": attrs.get("TOOLS_Name", ""),
|
|
30928
|
+
"modifier_type": attrs.get("TOOLS_RegID", "")},
|
|
30929
|
+
remediation="Drive the existing modifier tool, or "
|
|
30930
|
+
"disconnect(tool_name, input_name) first.",
|
|
30931
|
+
)
|
|
30932
|
+
# Same undo bracket as add_keyframe; no comp.Lock() — a lock is for
|
|
30933
|
+
# value writes and this is a graph edit, but see _FUSION_VALUE_WRITE_NOTE
|
|
30934
|
+
# for why writes under a lock are not trusted here.
|
|
30935
|
+
comp.StartUndo(f"Add modifier {modifier_id} to {input_name}")
|
|
30936
|
+
try:
|
|
30937
|
+
tool.AddModifier(input_name, modifier_id)
|
|
30938
|
+
# AddModifier's bool is unreliable through the Lua bridge (an unknown
|
|
30939
|
+
# attribute resolves to None). The readback is the evidence: the
|
|
30940
|
+
# input must now be connected to a modifier tool.
|
|
30941
|
+
try:
|
|
30942
|
+
out = tool[input_name].GetConnectedOutput()
|
|
30943
|
+
except Exception:
|
|
30944
|
+
out = None
|
|
30945
|
+
modifier_tool = out.GetTool() if out else None
|
|
30946
|
+
if modifier_tool is None:
|
|
30947
|
+
return _err(
|
|
30948
|
+
f"Could not attach {modifier_id!r} to "
|
|
30949
|
+
f"'{p['tool_name']}.{input_name}': no modifier is connected "
|
|
30950
|
+
"after AddModifier.",
|
|
30951
|
+
code="FUSION_ADD_MODIFIER_FAILED", category="api_error",
|
|
30952
|
+
retryable=False,
|
|
30953
|
+
reason="Fusion rejects a modifier whose registry ID it does not "
|
|
30954
|
+
"know for that input type, and reports it only as False.",
|
|
30955
|
+
remediation="Pass the modifier's REGISTRY ID, not its display "
|
|
30956
|
+
"name: the text Follower is 'StyledTextFollower' "
|
|
30957
|
+
"(add_modifier maps 'Follower' to it; measured on "
|
|
30958
|
+
"Studio 19.1.3.7). Spline modifiers are "
|
|
30959
|
+
"'BezierSpline' and, for Point inputs, 'Path'.",
|
|
30960
|
+
)
|
|
30961
|
+
attrs = modifier_tool.GetAttrs() or {}
|
|
30962
|
+
return _ok(
|
|
30963
|
+
tool_name=p["tool_name"], input_name=input_name,
|
|
30964
|
+
requested=requested, modifier_id=modifier_id,
|
|
30965
|
+
modifier_tool=attrs.get("TOOLS_Name", ""),
|
|
30966
|
+
modifier_type=attrs.get("TOOLS_RegID", modifier_id),
|
|
30967
|
+
next="Drive the modifier tool with set_input / add_keyframe "
|
|
30968
|
+
"(e.g. its Delay input for a per-character stagger).",
|
|
30969
|
+
)
|
|
30970
|
+
finally:
|
|
30971
|
+
comp.EndUndo(True)
|
|
30972
|
+
|
|
30871
30973
|
elif action == "get_keyframes":
|
|
30872
30974
|
tool = comp.FindTool(p["tool_name"])
|
|
30873
30975
|
if not tool:
|
|
@@ -31057,7 +31159,7 @@ def fusion_comp(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[st
|
|
|
31057
31159
|
"add_tool","delete_tool","get_tool_list","find_tool",
|
|
31058
31160
|
"connect","disconnect","get_inputs","get_outputs",
|
|
31059
31161
|
"set_input","get_input","set_attrs","get_attrs",
|
|
31060
|
-
"add_keyframe","get_keyframes","delete_keyframe",
|
|
31162
|
+
"add_keyframe","add_modifier","get_keyframes","delete_keyframe",
|
|
31061
31163
|
"get_comp_info","set_frame_range","get_frame_range","render",
|
|
31062
31164
|
"start_undo","end_undo",
|
|
31063
31165
|
"get_position","set_position","copy_tool","auto_arrange",
|
package/src/utils/api_truth.py
CHANGED
|
@@ -3414,6 +3414,32 @@ API_TRUTH: List[Dict[str, Any]] = [
|
|
|
3414
3414
|
"mitigation": ["project_manager.archive", "project_manager.safe_project_archive",
|
|
3415
3415
|
"archive_project"],
|
|
3416
3416
|
},
|
|
3417
|
+
{
|
|
3418
|
+
"symbol": "Tool.AddModifier",
|
|
3419
|
+
"object": "Fusion Tool",
|
|
3420
|
+
"signature": "(inputName, modifierRegID) -> bool",
|
|
3421
|
+
"reality": "The second argument is the modifier's REGISTRY ID, not its "
|
|
3422
|
+
"display name. On a TextPlus StyledText input, "
|
|
3423
|
+
"AddModifier('StyledText', 'Follower') and 'TextFollower' return "
|
|
3424
|
+
"False and attach nothing; 'StyledTextFollower' returns True, "
|
|
3425
|
+
"creates a tool named Follower1 of that ID and connects it to the "
|
|
3426
|
+
"input. Spline modifiers already go by registry ID (BezierSpline, "
|
|
3427
|
+
"Path). Through the Lua bridge the bool is not reliable evidence; "
|
|
3428
|
+
"the input's connected-output readback is.",
|
|
3429
|
+
"recommended": "Pass the registry ID. fusion_comp add_modifier maps 'Follower' "
|
|
3430
|
+
"to 'StyledTextFollower', verifies by readback and returns the "
|
|
3431
|
+
"created modifier tool so it can be driven with set_input / "
|
|
3432
|
+
"add_keyframe (Delay for a per-character stagger).",
|
|
3433
|
+
"tags": ["fusion", "naming", "silent-failure"],
|
|
3434
|
+
"verified_on": "DaVinci Resolve Studio 19.1.3.7",
|
|
3435
|
+
"measured": "2026-09-19 on a disposable timeline: "
|
|
3436
|
+
"InsertFusionCompositionIntoTimeline, AddTool('TextPlus'), then "
|
|
3437
|
+
"AddModifier('StyledText', X) for X in Follower / "
|
|
3438
|
+
"StyledTextFollower / TextFollower, with a GetToolList diff and "
|
|
3439
|
+
"StyledText.GetConnectedOutput().GetTool() readback after each; "
|
|
3440
|
+
"only StyledTextFollower attached (new tool Follower1).",
|
|
3441
|
+
"mitigation": ["fusion_comp.add_modifier", "fusion_comp.add_keyframe"],
|
|
3442
|
+
},
|
|
3417
3443
|
|
|
3418
3444
|
]
|
|
3419
3445
|
|
|
@@ -3474,6 +3500,7 @@ ACTION_SYMBOLS: Dict[Tuple[str, str], List[str]] = {
|
|
|
3474
3500
|
("timeline_item_color", "safe_export_lut"): ["TimelineItem.ExportLUT"],
|
|
3475
3501
|
("timeline", "duplicate"): ["Timeline.DuplicateTimeline"],
|
|
3476
3502
|
("project_manager", "archive"): ["ProjectManager.ArchiveProject"],
|
|
3503
|
+
("fusion_comp", "add_modifier"): ["Tool.AddModifier"],
|
|
3477
3504
|
("project_manager", "safe_project_archive"): ["ProjectManager.ArchiveProject"],
|
|
3478
3505
|
}
|
|
3479
3506
|
|
|
@@ -96,7 +96,7 @@ DESTRUCTIVE_ACTIONS_BY_TOOL: Dict[str, FrozenSet[str]] = {
|
|
|
96
96
|
# them. Plus the 21.0 AI deblur, rated MEDIUM: it creates media, and is
|
|
97
97
|
# registered so it is audited and its dry run is honest.
|
|
98
98
|
"folder": frozenset({"remove_motion_blur"}),
|
|
99
|
-
"fusion_comp": frozenset({"delete_keyframe", "delete_tool"}),
|
|
99
|
+
"fusion_comp": frozenset({"add_modifier", "delete_keyframe", "delete_tool"}),
|
|
100
100
|
"gallery_stills": frozenset({"delete_stills"}),
|
|
101
101
|
"media_pool_item": frozenset({"remove_motion_blur"}),
|
|
102
102
|
"media_pool_item_markers": frozenset({
|
|
@@ -203,6 +203,9 @@ class RiskClassificationHook(LifecycleHook):
|
|
|
203
203
|
#: them unrecognised, i.e. it warns that the risk is unestablished for the
|
|
204
204
|
#: actions whose risk is the best established of any we dispatch.
|
|
205
205
|
_LOW_RISK_ACTIONS: Set[Tuple[str, str]] = {
|
|
206
|
+
# Attaching a modifier to one Fusion input: one undo step, and
|
|
207
|
+
# disconnect(tool, input) removes it. (#250)
|
|
208
|
+
("fusion_comp", "add_modifier"),
|
|
206
209
|
("dctl", "encrypt_native"), # Creates a new file; never replaces existing content.
|
|
207
210
|
("timeline_markers", "add"),
|
|
208
211
|
("timeline_markers", "update_custom_data"),
|