davinci-resolve-mcp 2.24.0 → 2.25.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 +130 -4
- package/README.md +6 -1
- package/bin/davinci-resolve-mcp.mjs +4 -1
- package/docs/SKILL.md +176 -18
- package/docs/guides/color-decision-guide.md +40 -0
- package/docs/guides/control-panel.md +128 -0
- package/docs/guides/editorial-decision-guide.md +29 -0
- package/docs/reference/api-coverage.md +8 -2
- package/install.py +291 -24
- package/package.json +1 -1
- package/src/analysis_dashboard.py +2535 -35
- package/src/granular/common.py +1 -1
- package/src/granular/media_pool_item.py +54 -4
- package/src/server.py +2319 -86
- package/src/utils/analysis_caps.py +669 -0
- package/src/utils/analysis_memory.py +1 -1
- package/src/utils/analysis_runs.py +302 -0
- package/src/utils/brain_edits.py +380 -0
- package/src/utils/destructive_hook.py +615 -0
- package/src/utils/failure_tracker.py +119 -0
- package/src/utils/media_analysis.py +1687 -56
- package/src/utils/media_analysis_jobs.py +3 -0
- package/src/utils/media_pool_changes.py +116 -0
- package/src/utils/timeline_brain_db.py +475 -0
- package/src/utils/timeline_versioning.py +648 -0
- package/src/utils/update_check.py +72 -6
- package/docs/design/control-panel-polish-gameplan.md +0 -238
- package/docs/design/v2-control-panel-design.md +0 -184
- package/docs/design/v2-implementation-gameplan.md +0 -593
- package/docs/design/v2-shot-schema-spec.md +0 -500
package/src/granular/common.py
CHANGED
|
@@ -80,7 +80,7 @@ if not logging.getLogger().handlers:
|
|
|
80
80
|
handlers=[logging.StreamHandler()],
|
|
81
81
|
)
|
|
82
82
|
|
|
83
|
-
VERSION = "2.
|
|
83
|
+
VERSION = "2.25.0"
|
|
84
84
|
logger = logging.getLogger("davinci-resolve-mcp")
|
|
85
85
|
logger.info(f"Starting DaVinci Resolve MCP Server v{VERSION}")
|
|
86
86
|
logger.info(f"Detected platform: {get_platform()}")
|
|
@@ -1,9 +1,42 @@
|
|
|
1
1
|
"""MediaPoolItem operations and metadata helpers."""
|
|
2
2
|
|
|
3
3
|
from src.granular.common import * # noqa: F401,F403
|
|
4
|
+
from src.utils.media_analysis import mark_registry_stale_for_clip as _mark_analysis_registry_stale
|
|
4
5
|
|
|
5
6
|
resolve = ResolveProxy()
|
|
6
7
|
|
|
8
|
+
|
|
9
|
+
def _invalidate_analysis_registry_for_clip(project, clip, *, reason: str) -> Optional[Dict[str, Any]]:
|
|
10
|
+
"""Best-effort: mark cached analysis stale after a Resolve clip replace.
|
|
11
|
+
|
|
12
|
+
Called from replace_clip / replace_media_pool_clip / preserve_sub_clip
|
|
13
|
+
after the Resolve API confirms the mutation. Failures here must NEVER
|
|
14
|
+
block the Resolve mutation — we return the result for diagnostics but
|
|
15
|
+
swallow exceptions so registry trouble does not corrupt the user's edit.
|
|
16
|
+
"""
|
|
17
|
+
if project is None or clip is None:
|
|
18
|
+
return None
|
|
19
|
+
try:
|
|
20
|
+
project_name = project.GetName() if hasattr(project, "GetName") else None
|
|
21
|
+
project_id = project.GetUniqueId() if hasattr(project, "GetUniqueId") else None
|
|
22
|
+
clip_id = clip.GetUniqueId() if hasattr(clip, "GetUniqueId") else None
|
|
23
|
+
media_id = clip.GetMediaId() if hasattr(clip, "GetMediaId") else None
|
|
24
|
+
source_file = None
|
|
25
|
+
try:
|
|
26
|
+
source_file = clip.GetClipProperty("File Path") if hasattr(clip, "GetClipProperty") else None
|
|
27
|
+
except Exception:
|
|
28
|
+
source_file = None
|
|
29
|
+
return _mark_analysis_registry_stale(
|
|
30
|
+
project_name=project_name,
|
|
31
|
+
project_id=project_id,
|
|
32
|
+
clip_id=clip_id,
|
|
33
|
+
media_id=media_id,
|
|
34
|
+
source_file=source_file,
|
|
35
|
+
reason=reason,
|
|
36
|
+
)
|
|
37
|
+
except Exception as exc: # noqa: BLE001 — never break a Resolve mutation
|
|
38
|
+
return {"success": False, "error": f"registry invalidation skipped: {type(exc).__name__}: {exc}"}
|
|
39
|
+
|
|
7
40
|
@mcp.tool(annotations=EXTERNAL_DESTRUCTIVE_TOOL)
|
|
8
41
|
def link_proxy_media(clip_name: str, proxy_file_path: str) -> str:
|
|
9
42
|
"""Link a proxy media file to a clip.
|
|
@@ -118,6 +151,9 @@ def replace_clip(clip_name: str, replacement_path: str) -> str:
|
|
|
118
151
|
try:
|
|
119
152
|
result = target_clip.ReplaceClip(replacement_path)
|
|
120
153
|
if result:
|
|
154
|
+
_invalidate_analysis_registry_for_clip(
|
|
155
|
+
current_project, target_clip, reason="replace_clip"
|
|
156
|
+
)
|
|
121
157
|
return f"Successfully replaced clip '{clip_name}' with '{replacement_path}'"
|
|
122
158
|
else:
|
|
123
159
|
return f"Failed to replace clip '{clip_name}'"
|
|
@@ -674,14 +710,21 @@ def replace_media_pool_clip(clip_id: str, new_file_path: str) -> Dict[str, Any]:
|
|
|
674
710
|
clip_id: Unique ID of the clip to replace.
|
|
675
711
|
new_file_path: Absolute path to the new media file.
|
|
676
712
|
"""
|
|
677
|
-
|
|
713
|
+
project, mp, err = _get_mp()
|
|
678
714
|
if err:
|
|
679
715
|
return err
|
|
680
716
|
clip = _find_clip_by_id(mp.GetRootFolder(), clip_id)
|
|
681
717
|
if not clip:
|
|
682
718
|
return {"error": f"Clip {clip_id} not found"}
|
|
683
719
|
result = clip.ReplaceClip(new_file_path)
|
|
684
|
-
|
|
720
|
+
response: Dict[str, Any] = {"success": bool(result)}
|
|
721
|
+
if result:
|
|
722
|
+
registry = _invalidate_analysis_registry_for_clip(
|
|
723
|
+
project, clip, reason="replace_media_pool_clip"
|
|
724
|
+
)
|
|
725
|
+
if registry is not None:
|
|
726
|
+
response["analysis_registry_invalidation"] = registry
|
|
727
|
+
return response
|
|
685
728
|
|
|
686
729
|
|
|
687
730
|
@mcp.tool()
|
|
@@ -692,7 +735,7 @@ def replace_media_pool_clip_preserve_sub_clip(clip_id: str, file_path: str) -> D
|
|
|
692
735
|
clip_id: Unique ID of the clip to replace.
|
|
693
736
|
file_path: Absolute path to the replacement media file.
|
|
694
737
|
"""
|
|
695
|
-
|
|
738
|
+
project, mp, err = _get_mp()
|
|
696
739
|
if err:
|
|
697
740
|
return err
|
|
698
741
|
clip = _find_clip_by_id(mp.GetRootFolder(), clip_id)
|
|
@@ -702,7 +745,14 @@ def replace_media_pool_clip_preserve_sub_clip(clip_id: str, file_path: str) -> D
|
|
|
702
745
|
if missing:
|
|
703
746
|
return missing
|
|
704
747
|
result = clip.ReplaceClipPreserveSubClip(file_path)
|
|
705
|
-
|
|
748
|
+
response: Dict[str, Any] = {"success": bool(result)}
|
|
749
|
+
if result:
|
|
750
|
+
registry = _invalidate_analysis_registry_for_clip(
|
|
751
|
+
project, clip, reason="replace_media_pool_clip_preserve_sub_clip"
|
|
752
|
+
)
|
|
753
|
+
if registry is not None:
|
|
754
|
+
response["analysis_registry_invalidation"] = registry
|
|
755
|
+
return response
|
|
706
756
|
|
|
707
757
|
|
|
708
758
|
@mcp.tool()
|