davinci-resolve-mcp 4.8.19 → 4.8.21
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/install.py +1 -1
- package/package.json +1 -1
- package/src/granular/common.py +1 -1
- package/src/granular/timeline.py +1 -1
- package/src/server.py +157 -157
- package/src/utils/api_truth.py +24 -0
- package/src/utils/dctl_templates.py +4 -2
- package/src/utils/fuse_templates.py +4 -2
- package/src/utils/media_analysis_jobs.py +4 -2
- package/src/utils/multicam.py +5 -3
- package/src/utils/project_properties.py +1 -1
package/src/utils/api_truth.py
CHANGED
|
@@ -2005,6 +2005,30 @@ API_TRUTH: List[Dict[str, Any]] = [
|
|
|
2005
2005
|
"when mirroring keep-ranges into clipInfos.",
|
|
2006
2006
|
"tags": ["timeline", "edit", "off-by-one", "readback"],
|
|
2007
2007
|
},
|
|
2008
|
+
{
|
|
2009
|
+
"symbol": "Timeline.GetEndFrame (exclusive bound)",
|
|
2010
|
+
"object": "Timeline",
|
|
2011
|
+
"signature": "() -> int",
|
|
2012
|
+
"reality": "GetEndFrame() is an EXCLUSIVE bound, one past the last "
|
|
2013
|
+
"frame: a timeline's frame count is "
|
|
2014
|
+
"GetEndFrame() - GetStartFrame(), not that plus one. "
|
|
2015
|
+
"Measured live on Studio 19.1.3.7 (2026-09-26): a "
|
|
2016
|
+
"timeline whose only full-length item is a 32742-frame "
|
|
2017
|
+
"clip (media-pool 'Frames' 32742, 'End' 32741) reads "
|
|
2018
|
+
"GetStartFrame() 0 / GetEndFrame() 32742, and that item's "
|
|
2019
|
+
"TimelineItem.GetEnd() is also 32742 - the same half-open "
|
|
2020
|
+
"convention as clipInfo endFrame above. Until v4.8.21 two "
|
|
2021
|
+
"readers here (granular get_current_timeline and "
|
|
2022
|
+
"get_project_info's per-timeline duration) added 1 and "
|
|
2023
|
+
"reported 601 frames for a 600-frame timeline while every "
|
|
2024
|
+
"other duration path in the server did not (PR #269).",
|
|
2025
|
+
"recommended": "duration_frames = GetEndFrame() - GetStartFrame(). "
|
|
2026
|
+
"The last frame index is GetEndFrame() - 1. Apply the "
|
|
2027
|
+
"same rule to TimelineItem.GetEnd(): an item occupies "
|
|
2028
|
+
"[GetStart(), GetEnd()).",
|
|
2029
|
+
"tags": ["timeline", "off-by-one", "readback", "duration"],
|
|
2030
|
+
"verified_on": "DaVinci Resolve Studio 19.1.3.7",
|
|
2031
|
+
},
|
|
2008
2032
|
{
|
|
2009
2033
|
"symbol": "MediaPool.ImportTimelineFromFile (internal sequence name overrides timelineName)",
|
|
2010
2034
|
"object": "MediaPool",
|
|
@@ -12,6 +12,8 @@ Resolve re-scan its LUT directory. ACES DCTLs are scanned only at startup.
|
|
|
12
12
|
|
|
13
13
|
from typing import Any, Dict, List, Optional
|
|
14
14
|
|
|
15
|
+
from src.utils.bool_params import coerce_bool
|
|
16
|
+
|
|
15
17
|
|
|
16
18
|
def header(name: str, kind: str) -> str:
|
|
17
19
|
"""Marker comment used by the dctl tool's `list` action."""
|
|
@@ -264,7 +266,7 @@ def aces_idt(name: str, options: Optional[Dict[str, Any]] = None) -> str:
|
|
|
264
266
|
Install requires a Resolve restart. See docs/notes/dctl-notes.md → "DCTL And ACES".
|
|
265
267
|
"""
|
|
266
268
|
options = options or {}
|
|
267
|
-
parametric =
|
|
269
|
+
parametric = coerce_bool(options.get("parametric"), False)
|
|
268
270
|
body = options.get("body") or (
|
|
269
271
|
" return make_float3(p_R, p_G, p_B);"
|
|
270
272
|
)
|
|
@@ -290,7 +292,7 @@ def aces_odt(name: str, options: Optional[Dict[str, Any]] = None) -> str:
|
|
|
290
292
|
Resolve which side of the pipeline this transform belongs to.
|
|
291
293
|
"""
|
|
292
294
|
options = options or {}
|
|
293
|
-
parametric =
|
|
295
|
+
parametric = coerce_bool(options.get("parametric"), False)
|
|
294
296
|
body = options.get("body") or (
|
|
295
297
|
" return make_float3(p_R, p_G, p_B);"
|
|
296
298
|
)
|
|
@@ -10,6 +10,8 @@ Reference: Fusion Fuse SDK (June 2023), Blackmagic Design.
|
|
|
10
10
|
|
|
11
11
|
from typing import Any, Dict, Optional
|
|
12
12
|
|
|
13
|
+
from src.utils.bool_params import coerce_bool
|
|
14
|
+
|
|
13
15
|
# Maps the public `type` argument to Fusion's internal ClassType enum.
|
|
14
16
|
CLASS_TYPES = {
|
|
15
17
|
"tool": "CT_Tool",
|
|
@@ -149,7 +151,7 @@ def per_pixel(name: str, options: Optional[Dict[str, Any]] = None) -> str:
|
|
|
149
151
|
if n_inputs not in (1, 2):
|
|
150
152
|
raise ValueError("per_pixel: inputs must be 1 or 2")
|
|
151
153
|
expression = options.get("expression", " return p1")
|
|
152
|
-
expose_amount =
|
|
154
|
+
expose_amount = coerce_bool(options.get("amount"), True)
|
|
153
155
|
|
|
154
156
|
pixel_args = "x, y, p1, p2" if n_inputs == 2 else "x, y, p1"
|
|
155
157
|
|
|
@@ -1021,7 +1023,7 @@ def channel_op(name: str, options: Optional[Dict[str, Any]] = None) -> str:
|
|
|
1021
1023
|
if operation not in valid_ops:
|
|
1022
1024
|
raise ValueError(f"Invalid channel_op operation '{operation}'. "
|
|
1023
1025
|
f"Valid: {list(valid_ops)}")
|
|
1024
|
-
rgba_only =
|
|
1026
|
+
rgba_only = coerce_bool(options.get("rgba_only"), True)
|
|
1025
1027
|
|
|
1026
1028
|
aux_channels = ""
|
|
1027
1029
|
aux_doc = ""
|
|
@@ -18,6 +18,8 @@ import time
|
|
|
18
18
|
from pathlib import Path
|
|
19
19
|
from typing import Any, Dict, Iterable, List, Optional, Tuple
|
|
20
20
|
|
|
21
|
+
from src.utils.bool_params import coerce_bool
|
|
22
|
+
|
|
21
23
|
from src.utils.media_analysis import (
|
|
22
24
|
ANALYSIS_VERSION,
|
|
23
25
|
build_analysis_index,
|
|
@@ -641,7 +643,7 @@ def _finish_job_if_complete(
|
|
|
641
643
|
_event(conn, job_id, "info", "Batch job completed", counts)
|
|
642
644
|
conn.commit()
|
|
643
645
|
summarize_reports(root)
|
|
644
|
-
if params.get("auto_build_index", True) and not index_already_refreshed:
|
|
646
|
+
if coerce_bool(params.get("auto_build_index"), True) and not index_already_refreshed:
|
|
645
647
|
_auto_build_index(conn, root, job_id, "Analysis index rebuilt")
|
|
646
648
|
|
|
647
649
|
|
|
@@ -781,7 +783,7 @@ def run_batch_job_slice(
|
|
|
781
783
|
conn.commit()
|
|
782
784
|
|
|
783
785
|
index_refreshed = False
|
|
784
|
-
if params.get("auto_build_index", True) and any(
|
|
786
|
+
if coerce_bool(params.get("auto_build_index"), True) and any(
|
|
785
787
|
row.get("status") in {"succeeded", "skipped"} for row in processed
|
|
786
788
|
):
|
|
787
789
|
_auto_build_index(conn, root, job_id, "Analysis index refreshed after batch slice")
|
package/src/utils/multicam.py
CHANGED
|
@@ -10,6 +10,8 @@ from __future__ import annotations
|
|
|
10
10
|
import re
|
|
11
11
|
from typing import Any, Callable, Dict, List, Optional, Tuple
|
|
12
12
|
|
|
13
|
+
from src.utils.bool_params import coerce_bool
|
|
14
|
+
|
|
13
15
|
|
|
14
16
|
FindClip = Callable[[Any, str], Any]
|
|
15
17
|
|
|
@@ -323,8 +325,8 @@ def build_multicam_setup_plan(root: Any, params: Dict[str, Any], find_clip: Find
|
|
|
323
325
|
if not sync_mode:
|
|
324
326
|
return None, _err("sync_mode must be stack_start, record_frame, or source_timecode")
|
|
325
327
|
|
|
326
|
-
include_video =
|
|
327
|
-
include_audio =
|
|
328
|
+
include_video = coerce_bool(params.get("include_video", params.get("includeVideo")), True)
|
|
329
|
+
include_audio = coerce_bool(params.get("include_audio", params.get("includeAudio")), False)
|
|
328
330
|
if not include_video and not include_audio:
|
|
329
331
|
return None, _err("At least one of include_video or include_audio must be true")
|
|
330
332
|
audio_mode = _normalize_audio_mode(params.get("audio_track_mode", params.get("audioTrackMode")), include_audio)
|
|
@@ -347,7 +349,7 @@ def build_multicam_setup_plan(root: Any, params: Dict[str, Any], find_clip: Find
|
|
|
347
349
|
angles: List[Dict[str, Any]] = []
|
|
348
350
|
max_video_track = 0
|
|
349
351
|
max_audio_track = 0
|
|
350
|
-
allow_negative =
|
|
352
|
+
allow_negative = coerce_bool(params.get("allow_negative_record_frame", params.get("allowNegativeRecordFrame")), False)
|
|
351
353
|
record_frame_mode = params.get("record_frame_mode", params.get("recordFrameMode", "relative"))
|
|
352
354
|
|
|
353
355
|
for index, raw in enumerate(raw_angles):
|
|
@@ -592,7 +592,7 @@ def get_project_info(project_obj) -> Dict[str, Any]:
|
|
|
592
592
|
timeline_info = {
|
|
593
593
|
"name": timeline.GetName(),
|
|
594
594
|
"isCurrent": timeline.GetName() == current_timeline_name,
|
|
595
|
-
"duration": timeline.GetEndFrame() - timeline.GetStartFrame()
|
|
595
|
+
"duration": timeline.GetEndFrame() - timeline.GetStartFrame()
|
|
596
596
|
}
|
|
597
597
|
project_info["timelines"].append(timeline_info)
|
|
598
598
|
|