davinci-resolve-mcp 2.23.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.
Files changed (92) hide show
  1. package/AGENTS.md +85 -0
  2. package/CHANGELOG.md +802 -0
  3. package/CLAUDE.md +15 -0
  4. package/LICENSE +21 -0
  5. package/README.md +159 -0
  6. package/SECURITY.md +53 -0
  7. package/bin/davinci-resolve-mcp.mjs +376 -0
  8. package/docs/README.md +56 -0
  9. package/docs/SKILL.md +1145 -0
  10. package/docs/authoring/fuse-dctl-authoring.md +242 -0
  11. package/docs/authoring/script-plugin-authoring.md +195 -0
  12. package/docs/contributing.md +82 -0
  13. package/docs/guides/color-decision-guide.md +387 -0
  14. package/docs/guides/editorial-decision-guide.md +136 -0
  15. package/docs/guides/media-analysis-guide.md +615 -0
  16. package/docs/guides/multicam-setup-guide.md +138 -0
  17. package/docs/install.md +198 -0
  18. package/docs/integrations/workflow-integrations.md +120 -0
  19. package/docs/kernels/README.md +28 -0
  20. package/docs/kernels/audio-fairlight-kernel.md +86 -0
  21. package/docs/kernels/color-grade-kernel.md +103 -0
  22. package/docs/kernels/extension-authoring-kernel.md +101 -0
  23. package/docs/kernels/fusion-composition-kernel.md +91 -0
  24. package/docs/kernels/media-pool-ingest-kernel.md +147 -0
  25. package/docs/kernels/project-lifecycle-kernel.md +120 -0
  26. package/docs/kernels/render-deliver-kernel.md +92 -0
  27. package/docs/kernels/review-annotation-kernel.md +110 -0
  28. package/docs/kernels/timeline-conform-interchange-kernel.md +99 -0
  29. package/docs/kernels/timeline-edit-kernel.md +189 -0
  30. package/docs/notes/codec-plugin-notes.md +136 -0
  31. package/docs/notes/dctl-notes.md +234 -0
  32. package/docs/notes/fusion-template-notes.md +136 -0
  33. package/docs/notes/lut-notes.md +136 -0
  34. package/docs/notes/openfx-notes.md +120 -0
  35. package/docs/process/release-process.md +152 -0
  36. package/docs/reference/api-coverage.md +488 -0
  37. package/docs/reference/resolve_scripting_api.txt +1012 -0
  38. package/examples/README.md +53 -0
  39. package/examples/markers/README.md +81 -0
  40. package/examples/media/README.md +94 -0
  41. package/examples/timeline/README.md +98 -0
  42. package/install.py +1196 -0
  43. package/package.json +52 -0
  44. package/scripts/audit_api_parity.py +275 -0
  45. package/scripts/live_media_analysis_polish_probe.py +65 -0
  46. package/src/__init__.py +3 -0
  47. package/src/analysis_dashboard.py +4936 -0
  48. package/src/control_panel.py +13 -0
  49. package/src/granular/__init__.py +17 -0
  50. package/src/granular/common.py +727 -0
  51. package/src/granular/folder.py +287 -0
  52. package/src/granular/gallery.py +306 -0
  53. package/src/granular/graph.py +309 -0
  54. package/src/granular/media_pool.py +679 -0
  55. package/src/granular/media_pool_item.py +852 -0
  56. package/src/granular/media_storage.py +179 -0
  57. package/src/granular/project.py +1594 -0
  58. package/src/granular/resolve_control.py +521 -0
  59. package/src/granular/timeline.py +1074 -0
  60. package/src/granular/timeline_item.py +2251 -0
  61. package/src/resolve_mcp_server.py +43 -0
  62. package/src/server.py +15691 -0
  63. package/src/utils/__init__.py +3 -0
  64. package/src/utils/app_control.py +319 -0
  65. package/src/utils/audio_fairlight_live_probe.py +263 -0
  66. package/src/utils/cdl.py +20 -0
  67. package/src/utils/cloud_operations.py +192 -0
  68. package/src/utils/color_grade_live_probe.py +444 -0
  69. package/src/utils/dctl_templates.py +368 -0
  70. package/src/utils/extension_authoring_live_probe.py +292 -0
  71. package/src/utils/fuse_templates.py +1968 -0
  72. package/src/utils/fusion_composition_live_probe.py +284 -0
  73. package/src/utils/layout_presets.py +333 -0
  74. package/src/utils/mcp_stdio.py +32 -0
  75. package/src/utils/media_analysis.py +3618 -0
  76. package/src/utils/media_analysis_jobs.py +796 -0
  77. package/src/utils/media_pool_ingest_live_probe.py +592 -0
  78. package/src/utils/multicam.py +393 -0
  79. package/src/utils/object_inspection.py +287 -0
  80. package/src/utils/platform.py +157 -0
  81. package/src/utils/project_lifecycle_live_probe.py +376 -0
  82. package/src/utils/project_properties.py +601 -0
  83. package/src/utils/render_deliver_live_probe.py +384 -0
  84. package/src/utils/resolve_connection.py +77 -0
  85. package/src/utils/review_annotation_live_probe.py +352 -0
  86. package/src/utils/script_templates.py +1193 -0
  87. package/src/utils/sync_detection.py +887 -0
  88. package/src/utils/timeline_conform_live_probe.py +280 -0
  89. package/src/utils/timeline_kernel_live_probe.py +1091 -0
  90. package/src/utils/timeline_kernel_probe.py +185 -0
  91. package/src/utils/timeline_title_text.py +87 -0
  92. package/src/utils/update_check.py +610 -0
@@ -0,0 +1,352 @@
1
+ #!/usr/bin/env python3
2
+ """Live Review Annotation boundary probe."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import json
7
+ import platform
8
+ import shutil
9
+ import subprocess
10
+ import sys
11
+ import tempfile
12
+ import time
13
+ from pathlib import Path
14
+ from typing import Any, Dict, Optional
15
+
16
+ from src.utils.timeline_kernel_probe import ProbeRecorder, render_markdown_report, utc_timestamp
17
+
18
+
19
+ def _require_success(label: str, result: Dict[str, Any]) -> Dict[str, Any]:
20
+ if not isinstance(result, dict):
21
+ raise AssertionError(f"{label}: expected dict, got {result!r}")
22
+ if result.get("error"):
23
+ raise AssertionError(f"{label}: {result['error']}")
24
+ if "success" in result and result["success"] is not True:
25
+ raise AssertionError(f"{label}: expected success=True, got {result!r}")
26
+ return result
27
+
28
+
29
+ def _record_tool_result(
30
+ recorder: ProbeRecorder,
31
+ category: str,
32
+ name: str,
33
+ result: Dict[str, Any],
34
+ *,
35
+ expected_boundary: bool = False,
36
+ ) -> None:
37
+ if not isinstance(result, dict):
38
+ recorder.record(category, name, "error", details={"reason": "non-dict result", "result": repr(result)})
39
+ return
40
+ if result.get("error"):
41
+ recorder.record(
42
+ category,
43
+ name,
44
+ "unsupported" if expected_boundary else "error",
45
+ details={"reason": result.get("error"), "expected_boundary": expected_boundary},
46
+ evidence=result,
47
+ )
48
+ return
49
+ if "success" in result and result["success"] is not True:
50
+ recorder.record(
51
+ category,
52
+ name,
53
+ "unsupported" if expected_boundary else "partially_supported",
54
+ details={"reason": "success returned false", "expected_boundary": expected_boundary},
55
+ evidence=result,
56
+ )
57
+ return
58
+ recorder.record(category, name, "supported", evidence=result)
59
+
60
+
61
+ def _run_ffmpeg(args: list[str]) -> None:
62
+ subprocess.run(["ffmpeg", "-hide_banner", "-loglevel", "error", *args], check=True)
63
+
64
+
65
+ def _make_synthetic_video(work_dir: Path) -> Path:
66
+ video = work_dir / "review_annotation_probe.mov"
67
+ _run_ffmpeg(
68
+ [
69
+ "-f",
70
+ "lavfi",
71
+ "-i",
72
+ "testsrc2=size=320x180:rate=24:duration=4",
73
+ "-f",
74
+ "lavfi",
75
+ "-i",
76
+ "sine=frequency=550:sample_rate=48000:duration=4",
77
+ "-shortest",
78
+ "-pix_fmt",
79
+ "yuv420p",
80
+ "-c:v",
81
+ "libx264",
82
+ "-c:a",
83
+ "aac",
84
+ "-y",
85
+ str(video),
86
+ ]
87
+ )
88
+ return video
89
+
90
+
91
+ def _first_imported_clip(imported_items):
92
+ for item in imported_items or []:
93
+ try:
94
+ if item.GetUniqueId():
95
+ return item
96
+ except Exception:
97
+ pass
98
+ return None
99
+
100
+
101
+ def run_probe(server, output_dir: Path, keep_open: bool = False) -> Dict[str, Any]:
102
+ output_dir.mkdir(parents=True, exist_ok=True)
103
+ work_dir = Path(tempfile.mkdtemp(prefix="mcp_review_annotation_probe_"))
104
+ project_name = f"_mcp_review_annotation_probe_{int(time.time())}"
105
+ timeline_name = "Review Annotation Probe Timeline"
106
+ recorder = ProbeRecorder()
107
+ created_project = False
108
+ delete_result: Optional[Dict[str, Any]] = None
109
+
110
+ metadata: Dict[str, Any] = {
111
+ "title": "Review Annotation Kernel Capability Probe",
112
+ "timestamp_utc": utc_timestamp(),
113
+ "python": sys.version,
114
+ "platform": platform.platform(),
115
+ "output_dir": str(output_dir),
116
+ "project_name": project_name,
117
+ }
118
+
119
+ try:
120
+ version = _require_success("resolve_control.get_version", server.resolve_control("get_version"))
121
+ metadata.update(
122
+ {
123
+ "product": version.get("product"),
124
+ "version": version.get("version"),
125
+ "version_string": version.get("version_string"),
126
+ }
127
+ )
128
+ print(f"Connected to {metadata['product']} {metadata['version_string']}")
129
+
130
+ _require_success("project_manager.create", server.project_manager("create", {"name": project_name}))
131
+ created_project = True
132
+ print(f"Created disposable project: {project_name}")
133
+ server.resolve_control("open_page", {"page": "edit"})
134
+
135
+ video = _make_synthetic_video(work_dir)
136
+ metadata["synthetic_media"] = {"video": str(video)}
137
+ print(f"Generated synthetic media under: {work_dir}")
138
+
139
+ resolve = server.get_resolve()
140
+ project = resolve.GetProjectManager().GetCurrentProject()
141
+ media_pool = project.GetMediaPool()
142
+ imported = media_pool.ImportMedia([str(video)]) or []
143
+ clip = _first_imported_clip(imported)
144
+ if not clip:
145
+ raise AssertionError("Failed to import synthetic review media")
146
+ clip_id = clip.GetUniqueId()
147
+ metadata["clip_id"] = clip_id
148
+ timeline = media_pool.CreateTimelineFromClips(timeline_name, [clip])
149
+ if not timeline:
150
+ raise AssertionError("Failed to create review annotation timeline")
151
+ project.SetCurrentTimeline(timeline)
152
+ timeline.SetCurrentTimecode("01:00:00:01")
153
+ print(f"Created timeline: {timeline_name}")
154
+
155
+ _record_tool_result(recorder, "capabilities", "annotation_capabilities", server.timeline_markers("annotation_capabilities"))
156
+ _record_tool_result(
157
+ recorder,
158
+ "normalization",
159
+ "normalize_marker_payload",
160
+ server.timeline_markers(
161
+ "normalize_marker_payload",
162
+ {"frameId": "24", "color": "green", "label": "Normalized", "comment": "Payload", "customData": "normalized-24"},
163
+ ),
164
+ )
165
+ _record_tool_result(
166
+ recorder,
167
+ "normalization",
168
+ "invalid_color_boundary",
169
+ server.timeline_markers("normalize_marker_payload", {"frame": 25, "color": "Invisible"}),
170
+ expected_boundary=True,
171
+ )
172
+
173
+ timeline_markers = [
174
+ ("add_frame", {"frame": 4, "color": "Blue", "name": "Frame", "note": "frame alias", "custom_data": "tl-frame"}),
175
+ ("add_frame_id", {"frame_id": 8, "color": "Green", "name": "Frame ID", "note": "frame_id alias", "custom_data": "tl-frame-id"}),
176
+ ("add_frameId", {"frameId": 12, "color": "Yellow", "name": "FrameId", "note": "frameId alias", "custom_data": "tl-frameId"}),
177
+ ("add_timecode", {"timecode": "00:00:00:16", "color": "Red", "name": "Timecode", "note": "timecode alias", "custom_data": "tl-timecode"}),
178
+ ("add_current_playhead", {"color": "Cyan", "name": "Current", "note": "current playhead", "custom_data": "tl-current"}),
179
+ ]
180
+ for name, params in timeline_markers:
181
+ _record_tool_result(recorder, "timeline_markers", name, server.timeline_markers("add", params))
182
+
183
+ _record_tool_result(recorder, "timeline_markers", "get_all", server.timeline_markers("get_all"))
184
+ _record_tool_result(
185
+ recorder,
186
+ "timeline_markers",
187
+ "get_by_custom_data",
188
+ server.timeline_markers("get_by_custom_data", {"custom_data": "tl-frame"}),
189
+ )
190
+ _record_tool_result(
191
+ recorder,
192
+ "timeline_markers",
193
+ "update_custom_data",
194
+ server.timeline_markers("update_custom_data", {"frame": 4, "custom_data": "tl-frame-updated"}),
195
+ )
196
+ _record_tool_result(
197
+ recorder,
198
+ "timeline_markers",
199
+ "get_custom_data",
200
+ server.timeline_markers("get_custom_data", {"frame": 4}),
201
+ )
202
+
203
+ for index, color in enumerate(
204
+ ["Blue", "Cyan", "Green", "Yellow", "Red", "Pink", "Purple", "Fuchsia", "Rose", "Lavender", "Sky", "Mint", "Lemon", "Sand", "Cocoa", "Cream"],
205
+ start=40,
206
+ ):
207
+ _record_tool_result(
208
+ recorder,
209
+ "marker_colors",
210
+ f"color_{color}",
211
+ server.timeline_markers("add", {"frame": index, "color": color, "name": color, "custom_data": f"color-{color.lower()}"}),
212
+ )
213
+
214
+ _record_tool_result(
215
+ recorder,
216
+ "copy",
217
+ "copy_timeline_to_item",
218
+ server.timeline_markers(
219
+ "copy_annotations",
220
+ {"source": {"scope": "timeline"}, "target": {"scope": "timeline_item", "track_type": "video", "track_index": 1, "item_index": 0}},
221
+ ),
222
+ )
223
+ _record_tool_result(
224
+ recorder,
225
+ "timeline_item",
226
+ "sync_marker_custom_data",
227
+ server.timeline_markers(
228
+ "sync_marker_custom_data",
229
+ {"scope": "timeline_item", "track_type": "video", "track_index": 1, "item_index": 0, "frame": 4, "custom_data": "item-synced"},
230
+ ),
231
+ )
232
+ _record_tool_result(
233
+ recorder,
234
+ "timeline_item",
235
+ "add_flag",
236
+ server.timeline_item_markers("add_flag", {"track_type": "video", "track_index": 1, "item_index": 0, "color": "Blue"}),
237
+ )
238
+ _record_tool_result(
239
+ recorder,
240
+ "timeline_item",
241
+ "get_flags",
242
+ server.timeline_item_markers("get_flags", {"track_type": "video", "track_index": 1, "item_index": 0}),
243
+ )
244
+ _record_tool_result(
245
+ recorder,
246
+ "timeline_item",
247
+ "set_clip_color",
248
+ server.timeline_item_markers("set_clip_color", {"track_type": "video", "track_index": 1, "item_index": 0, "color": "Blue"}),
249
+ )
250
+ _record_tool_result(
251
+ recorder,
252
+ "timeline_item",
253
+ "get_clip_color",
254
+ server.timeline_item_markers("get_clip_color", {"track_type": "video", "track_index": 1, "item_index": 0}),
255
+ )
256
+
257
+ _record_tool_result(
258
+ recorder,
259
+ "media_pool_item",
260
+ "marker_add",
261
+ server.media_pool_item_markers(
262
+ "add",
263
+ {"clip_id": clip_id, "frame": 20, "color": "Blue", "name": "Media", "note": "media marker", "custom_data": "mpi-20"},
264
+ ),
265
+ )
266
+ _record_tool_result(
267
+ recorder,
268
+ "media_pool_item",
269
+ "add_flag",
270
+ server.media_pool_item_markers("add_flag", {"clip_id": clip_id, "color": "Blue"}),
271
+ )
272
+ _record_tool_result(
273
+ recorder,
274
+ "media_pool_item",
275
+ "set_clip_color",
276
+ server.media_pool_item("set_clip_color", {"clip_id": clip_id, "color": "Blue"}),
277
+ )
278
+ _record_tool_result(
279
+ recorder,
280
+ "move",
281
+ "move_media_pool_to_timeline",
282
+ server.timeline_markers(
283
+ "move_annotations",
284
+ {"source": {"scope": "media_pool_item", "clip_id": clip_id}, "target": {"scope": "timeline"}},
285
+ ),
286
+ )
287
+
288
+ _record_tool_result(recorder, "report", "probe_annotations", server.timeline_markers("probe_annotations"))
289
+ _record_tool_result(recorder, "report", "export_review_report", server.timeline_markers("export_review_report"))
290
+ _record_tool_result(recorder, "report", "annotation_boundary_report", server.timeline_markers("annotation_boundary_report"))
291
+
292
+ _record_tool_result(
293
+ recorder,
294
+ "cleanup",
295
+ "clear_timeline_item_annotations",
296
+ server.timeline_markers(
297
+ "clear_annotations_by_scope",
298
+ {"scope": "timeline_item", "track_type": "video", "track_index": 1, "item_index": 0, "all": True, "clear_flags": True, "clear_clip_color": True},
299
+ ),
300
+ )
301
+ _record_tool_result(
302
+ recorder,
303
+ "cleanup",
304
+ "clear_timeline_annotations",
305
+ server.timeline_markers("clear_annotations_by_scope", {"scope": "timeline", "all": True}),
306
+ )
307
+ _record_tool_result(
308
+ recorder,
309
+ "cleanup",
310
+ "clear_media_pool_flags",
311
+ server.media_pool_item_markers("clear_flags", {"clip_id": clip_id, "color": "Blue"}),
312
+ )
313
+ _record_tool_result(
314
+ recorder,
315
+ "cleanup",
316
+ "clear_media_pool_color",
317
+ server.media_pool_item("clear_clip_color", {"clip_id": clip_id}),
318
+ )
319
+
320
+ if keep_open:
321
+ server.project_manager("save")
322
+ print(f"LEFT PROJECT OPEN FOR INSPECTION: {project_name}")
323
+ created_project = False
324
+
325
+ finally:
326
+ if created_project:
327
+ server.project_manager("save")
328
+ server.project_manager("close")
329
+ delete_result = server.project_manager("delete", {"name": project_name})
330
+ print(f"Deleted disposable project: {delete_result}")
331
+
332
+ report = recorder.to_report(
333
+ metadata,
334
+ {
335
+ "json": str(output_dir / "review-annotation-probe.json"),
336
+ "markdown": str(output_dir / "review-annotation-probe.md"),
337
+ },
338
+ )
339
+ json_path = output_dir / "review-annotation-probe.json"
340
+ markdown_path = output_dir / "review-annotation-probe.md"
341
+ json_path.write_text(json.dumps(report, indent=2, sort_keys=True), encoding="utf-8")
342
+ markdown_path.write_text(render_markdown_report(report), encoding="utf-8")
343
+ print(f"Wrote JSON report: {json_path}")
344
+ print(f"Wrote Markdown report: {markdown_path}")
345
+ print(f"Counts: {json.dumps(report['counts'], sort_keys=True)}")
346
+ if not keep_open:
347
+ shutil.rmtree(work_dir, ignore_errors=True)
348
+ print(f"Removed synthetic media directory: {work_dir}")
349
+
350
+ if delete_result and delete_result.get("success") is not True:
351
+ raise AssertionError(f"Cleanup failed for {project_name}: {delete_result!r}")
352
+ return report