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.
Files changed (32) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +2 -2
  4. package/docs/guides/headless-edit-loop.md +15 -0
  5. package/install.py +33 -12
  6. package/package.json +1 -1
  7. package/resolve-advanced/server/db-patch.mjs +15 -3
  8. package/resolve-advanced/server/offline-ref-db.mjs +8 -2
  9. package/resolve-advanced/vendor/drp-format/__tests__/framerate-encoding.test.js +47 -0
  10. package/resolve-advanced/vendor/drp-format/drp-packager.js +1 -0
  11. package/resolve-advanced/vendor/drp-format/seq-container-builder.js +11 -3
  12. package/resolve-advanced/vendor/drp-format/xml-builder.js +23 -19
  13. package/scripts/doctor.py +85 -39
  14. package/src/granular/common.py +1 -1
  15. package/src/granular/media_pool.py +15 -7
  16. package/src/granular/timeline.py +10 -5
  17. package/src/granular/timeline_item.py +21 -7
  18. package/src/server.py +497 -93
  19. package/src/utils/api_truth.py +17 -5
  20. package/src/utils/app_control.py +11 -1
  21. package/src/utils/audio_fairlight_live_probe.py +8 -1
  22. package/src/utils/color_grade_live_probe.py +8 -1
  23. package/src/utils/fusion_composition_live_probe.py +8 -1
  24. package/src/utils/probe_catalogue.py +15 -3
  25. package/src/utils/project_cleanup.py +17 -3
  26. package/src/utils/render_deliver_live_probe.py +8 -1
  27. package/src/utils/resolve_bridge_client.py +10 -1
  28. package/src/utils/resolve_writes.py +79 -0
  29. package/src/utils/review_annotation_live_probe.py +8 -1
  30. package/src/utils/timeline_conform_live_probe.py +8 -1
  31. package/src/utils/timeline_kernel_live_probe.py +7 -1
  32. 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
- try:
276
- project.SetCurrentTimeline(timeline)
277
- except Exception:
278
- pass
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
- pass
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"):
@@ -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
- try:
1042
- if project:
1043
- project.SetCurrentTimeline(tl)
1044
- except Exception:
1045
- logger.debug("Could not set newly created timeline current", exc_info=True)
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
- # We need to delete and re-add the keyframe with the new value
996
- timeline_item.DeleteKeyframe(property_name, frame)
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
- timeline_item.DeleteKeyframe(property_name, frame)
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])