davinci-resolve-mcp 2.104.0 → 2.104.2
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 +77 -0
- package/README.md +1 -1
- package/README.zh-CN.md +2 -2
- package/docs/guides/headless-edit-loop.md +15 -0
- package/install.py +33 -12
- package/package.json +1 -1
- package/resolve-advanced/server/db-patch.mjs +15 -3
- package/resolve-advanced/server/offline-ref-db.mjs +8 -2
- package/resolve-advanced/vendor/drp-format/__tests__/framerate-encoding.test.js +47 -0
- package/resolve-advanced/vendor/drp-format/drp-packager.js +1 -0
- package/resolve-advanced/vendor/drp-format/seq-container-builder.js +11 -3
- package/resolve-advanced/vendor/drp-format/xml-builder.js +23 -19
- package/scripts/doctor.py +85 -39
- package/src/granular/common.py +1 -1
- package/src/granular/media_pool.py +15 -7
- package/src/granular/timeline.py +10 -5
- package/src/granular/timeline_item.py +21 -7
- package/src/server.py +497 -93
- package/src/utils/api_truth.py +17 -5
- package/src/utils/app_control.py +11 -1
- package/src/utils/audio_fairlight_live_probe.py +8 -1
- package/src/utils/color_grade_live_probe.py +8 -1
- package/src/utils/fusion_composition_live_probe.py +8 -1
- package/src/utils/probe_catalogue.py +15 -3
- package/src/utils/project_cleanup.py +17 -3
- package/src/utils/render_deliver_live_probe.py +8 -1
- package/src/utils/resolve_bridge_client.py +10 -1
- package/src/utils/resolve_writes.py +79 -0
- package/src/utils/review_annotation_live_probe.py +8 -1
- package/src/utils/timeline_conform_live_probe.py +8 -1
- package/src/utils/timeline_kernel_live_probe.py +7 -1
- package/src/utils/timeline_versioning.py +15 -2
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from src.granular.common import * # noqa: F401,F403
|
|
4
4
|
from src.utils.multicam import build_multicam_setup_plan
|
|
5
|
+
from src.utils.resolve_writes import set_current_timeline, describe_switch_failure
|
|
5
6
|
|
|
6
7
|
resolve = ResolveProxy()
|
|
7
8
|
|
|
@@ -272,15 +273,22 @@ def setup_multicam_timeline(
|
|
|
272
273
|
timeline = mp.CreateEmptyTimeline(plan["name"])
|
|
273
274
|
if not timeline:
|
|
274
275
|
return {"error": f"Failed to create multicam setup timeline: {plan['name']}"}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
276
|
+
switched, switch_detail = set_current_timeline(project, timeline)
|
|
277
|
+
if not switched:
|
|
278
|
+
return {"error": describe_switch_failure(switch_detail, "stacking the angles"),
|
|
279
|
+
"switch": switch_detail}
|
|
280
|
+
start_timecode_set = None
|
|
279
281
|
if plan.get("start_timecode"):
|
|
282
|
+
# A refused start timecode leaves the timeline at 01:00:00:00 while every
|
|
283
|
+
# recordFrame below was computed against the requested one, so the whole
|
|
284
|
+
# stack lands at the wrong absolute time. Reported, not swallowed.
|
|
280
285
|
try:
|
|
281
|
-
timeline.SetStartTimecode(plan["start_timecode"])
|
|
282
|
-
except Exception:
|
|
283
|
-
|
|
286
|
+
start_timecode_set = bool(timeline.SetStartTimecode(plan["start_timecode"]))
|
|
287
|
+
except Exception as exc:
|
|
288
|
+
return {"error": f"SetStartTimecode('{plan['start_timecode']}') raised: {exc}"}
|
|
289
|
+
if not start_timecode_set:
|
|
290
|
+
return {"error": f"SetStartTimecode('{plan['start_timecode']}') was refused; "
|
|
291
|
+
f"the angle stack would land at the wrong absolute time"}
|
|
284
292
|
|
|
285
293
|
video_tracks = _ensure_timeline_tracks_for_multicam(timeline, "video", plan.get("max_video_track", 0))
|
|
286
294
|
if not video_tracks.get("success"):
|
package/src/granular/timeline.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from src.granular.common import * # noqa: F401,F403
|
|
4
4
|
|
|
5
5
|
from src.utils.page_lock import color_page_for_thumbnails
|
|
6
|
+
from src.utils.resolve_writes import set_current_timeline, describe_switch_failure
|
|
6
7
|
|
|
7
8
|
resolve = ResolveProxy()
|
|
8
9
|
|
|
@@ -1038,11 +1039,15 @@ def create_timeline_from_clips(
|
|
|
1038
1039
|
tl = mp.CreateEmptyTimeline(name)
|
|
1039
1040
|
if not tl:
|
|
1040
1041
|
return {"success": False, "error": "Failed to create timeline"}
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1042
|
+
if project:
|
|
1043
|
+
# The appends below go to the CURRENT timeline, so this is a
|
|
1044
|
+
# precondition for the whole build, not a convenience.
|
|
1045
|
+
switched, switch_detail = set_current_timeline(project, tl)
|
|
1046
|
+
if not switched:
|
|
1047
|
+
return {"success": False,
|
|
1048
|
+
"error": describe_switch_failure(switch_detail,
|
|
1049
|
+
"appending the clip_infos"),
|
|
1050
|
+
"switch": switch_detail}
|
|
1046
1051
|
timeline_start = _timeline_start_frame(tl)
|
|
1047
1052
|
built = []
|
|
1048
1053
|
for i, ci in enumerate(clip_infos):
|
|
@@ -978,9 +978,15 @@ def modify_keyframe(timeline_item_id: str, property_name: str, frame: int, new_v
|
|
|
978
978
|
if new_frame < start_frame or new_frame > end_frame:
|
|
979
979
|
return f"Error: New frame {new_frame} is outside the item's range ({start_frame} to {end_frame})"
|
|
980
980
|
|
|
981
|
-
# Delete the keyframe at the current frame
|
|
981
|
+
# Delete the keyframe at the current frame. A discarded False here
|
|
982
|
+
# leaves the ORIGINAL keyframe in place; the AddKeyframe below then
|
|
983
|
+
# succeeds at the new frame and the tool reports "moved" for what is
|
|
984
|
+
# actually a copy.
|
|
982
985
|
current_value = timeline_item.GetPropertyAtKeyframeIndex(property_name, keyframe_index)
|
|
983
|
-
timeline_item.DeleteKeyframe(property_name, frame)
|
|
986
|
+
if not timeline_item.DeleteKeyframe(property_name, frame):
|
|
987
|
+
return (f"Failed to move keyframe for {property_name}: DeleteKeyframe "
|
|
988
|
+
f"refused frame {frame}, so the original is still there and "
|
|
989
|
+
f"nothing was moved")
|
|
984
990
|
|
|
985
991
|
# Add a new keyframe at the new frame position with the current value (or new value if specified)
|
|
986
992
|
value = new_value if new_value is not None else current_value
|
|
@@ -991,9 +997,13 @@ def modify_keyframe(timeline_item_id: str, property_name: str, frame: int, new_v
|
|
|
991
997
|
else:
|
|
992
998
|
return f"Failed to move keyframe for {property_name}"
|
|
993
999
|
else:
|
|
994
|
-
# Only changing the value, not the frame position
|
|
995
|
-
#
|
|
996
|
-
|
|
1000
|
+
# Only changing the value, not the frame position: delete and
|
|
1001
|
+
# re-add. A discarded False leaves the OLD value in place and
|
|
1002
|
+
# AddKeyframe on an occupied frame is not a value update.
|
|
1003
|
+
if not timeline_item.DeleteKeyframe(property_name, frame):
|
|
1004
|
+
return (f"Failed to update keyframe value for {property_name} at frame "
|
|
1005
|
+
f"{frame}: DeleteKeyframe refused, so the old value is still "
|
|
1006
|
+
f"there and nothing was changed")
|
|
997
1007
|
result = timeline_item.AddKeyframe(property_name, frame, new_value)
|
|
998
1008
|
|
|
999
1009
|
if result:
|
|
@@ -1171,8 +1181,12 @@ def set_keyframe_interpolation(timeline_item_id: str, property_name: str, frame:
|
|
|
1171
1181
|
value = timeline_item.GetPropertyAtKeyframeIndex(property_name, i)
|
|
1172
1182
|
break
|
|
1173
1183
|
|
|
1174
|
-
# Delete the old keyframe
|
|
1175
|
-
|
|
1184
|
+
# Delete the old keyframe. A discarded False leaves the original
|
|
1185
|
+
# interpolation in place and the tool still reports success.
|
|
1186
|
+
if not timeline_item.DeleteKeyframe(property_name, frame):
|
|
1187
|
+
return (f"Failed to set interpolation for {property_name} at frame {frame}: "
|
|
1188
|
+
f"DeleteKeyframe refused, so the original keyframe and its "
|
|
1189
|
+
f"interpolation are unchanged")
|
|
1176
1190
|
|
|
1177
1191
|
# Add a new keyframe with the same value but different interpolation
|
|
1178
1192
|
result = timeline_item.AddKeyframe(property_name, frame, value, interpolation_map[interpolation_type])
|