davinci-resolve-mcp 2.122.0 → 2.123.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/docs/guides/native-drt-authoring.md +7 -2
- package/install.py +1 -1
- package/package.json +1 -1
- package/src/granular/common.py +1 -1
- package/src/server.py +58 -1
|
@@ -54,7 +54,9 @@ window. `render.verify_output` covers the container-level checks.
|
|
|
54
54
|
| Fusion titles | `elements: [{type:'title', text}]` — **21-gen hosts only** | v2.108 |
|
|
55
55
|
|
|
56
56
|
`assemble_from_interchange` drives the same engine from an EDL / OTIO /
|
|
57
|
-
FCP7-XML / AAF plus a `sourceMap
|
|
57
|
+
FCP7-XML / AAF plus a `sourceMap` — all four formats are route-proven
|
|
58
|
+
end-to-end (parse → assemble → import → measured frames and RMS) — and
|
|
59
|
+
returns an honesty ledger
|
|
58
60
|
(`authoredTransitions`, `droppedTransitions` with reasons, `authoredRetimes`,
|
|
59
61
|
`flattenedRetimes`, `authoredAudioEvents`, `upperTrackCutsVideoOnly`).
|
|
60
62
|
|
|
@@ -99,7 +101,10 @@ Everything else stays in `droppedTransitions` with the reason.
|
|
|
99
101
|
## Verification checklist for a delivered .drt
|
|
100
102
|
|
|
101
103
|
1. `timeline.import_timeline_checked` — expect `linked == total` for media
|
|
102
|
-
(generators legitimately count as offline).
|
|
104
|
+
(generators legitimately count as offline). For `.drt`/`.drp` it also
|
|
105
|
+
cross-checks the files items ACTUALLY link against the archive's
|
|
106
|
+
`<MediaFilePath>` set and warns on a coarse-identity cross-link
|
|
107
|
+
(`cross_link_warning`) — `linked == total` alone cannot see one.
|
|
103
108
|
2. Render a probe range; check frame luma at cut boundaries, dissolve
|
|
104
109
|
midpoints (expect the blend average), and retime windows.
|
|
105
110
|
3. For audio: RMS per window (silence = -inf is a failed placement).
|
package/install.py
CHANGED
|
@@ -37,7 +37,7 @@ from src.utils.update_check import (
|
|
|
37
37
|
|
|
38
38
|
# ─── Version ──────────────────────────────────────────────────────────────────
|
|
39
39
|
|
|
40
|
-
VERSION = "2.
|
|
40
|
+
VERSION = "2.123.0"
|
|
41
41
|
# Only hard floor: mcp[cli] requires Python 3.10+. There is no upper bound —
|
|
42
42
|
# Resolve's scripting bridge loads into newer interpreters on recent builds
|
|
43
43
|
# (Python 3.14 verified against Resolve Studio 20.3.2). Older Resolve builds
|
package/package.json
CHANGED
package/src/granular/common.py
CHANGED
|
@@ -87,7 +87,7 @@ if not logging.getLogger().handlers:
|
|
|
87
87
|
handlers=[logging.StreamHandler()],
|
|
88
88
|
)
|
|
89
89
|
|
|
90
|
-
VERSION = "2.
|
|
90
|
+
VERSION = "2.123.0"
|
|
91
91
|
logger = logging.getLogger("davinci-resolve-mcp")
|
|
92
92
|
logger.info(f"Starting DaVinci Resolve MCP Server v{VERSION}")
|
|
93
93
|
logger.info(f"Detected platform: {get_platform()}")
|
package/src/server.py
CHANGED
|
@@ -11,7 +11,7 @@ Usage:
|
|
|
11
11
|
python src/server.py --full # Start the 353-tool granular server instead
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
|
-
VERSION = "2.
|
|
14
|
+
VERSION = "2.123.0"
|
|
15
15
|
|
|
16
16
|
import base64
|
|
17
17
|
import os
|
|
@@ -6966,6 +6966,49 @@ def _export_timeline_checked(tl, p: Dict[str, Any]):
|
|
|
6966
6966
|
return _run_maybe_background("timeline.export_timeline_checked", p, _work)
|
|
6967
6967
|
|
|
6968
6968
|
|
|
6969
|
+
def _drt_expected_media_paths(path: str):
|
|
6970
|
+
"""Unique <MediaFilePath> values referenced by a .drt/.drp's timeline clips.
|
|
6971
|
+
|
|
6972
|
+
Feeds the cross-link check: Resolve merges pool media by a COARSE identity
|
|
6973
|
+
across imports, so a clip can silently relink to a DIFFERENT pre-existing
|
|
6974
|
+
file (measured: item readback even shows the wrong clip name). The archive
|
|
6975
|
+
is the ground truth for which files the timeline meant.
|
|
6976
|
+
"""
|
|
6977
|
+
import zipfile as _zf
|
|
6978
|
+
paths = set()
|
|
6979
|
+
try:
|
|
6980
|
+
with _zf.ZipFile(path) as zf:
|
|
6981
|
+
for n in zf.namelist():
|
|
6982
|
+
if re.search(r"SeqContainer/.+\.xml$", n) or re.search(r"/SeqContainer\d*\.xml$", n):
|
|
6983
|
+
xml = zf.read(n).decode("utf-8", "replace")
|
|
6984
|
+
for m in re.finditer(r"<MediaFilePath>([^<]+)</MediaFilePath>", xml):
|
|
6985
|
+
paths.add(m.group(1))
|
|
6986
|
+
except Exception:
|
|
6987
|
+
return None
|
|
6988
|
+
return paths or None
|
|
6989
|
+
|
|
6990
|
+
|
|
6991
|
+
def _timeline_cross_link_check(tl, expected_paths):
|
|
6992
|
+
"""Compare the media files a timeline ACTUALLY links against the expected set."""
|
|
6993
|
+
actual = set()
|
|
6994
|
+
for track_type in ("video", "audio"):
|
|
6995
|
+
try:
|
|
6996
|
+
count = int(tl.GetTrackCount(track_type) or 0)
|
|
6997
|
+
except Exception:
|
|
6998
|
+
count = 0
|
|
6999
|
+
for i in range(1, count + 1):
|
|
7000
|
+
for item in (tl.GetItemListInTrack(track_type, i) or []):
|
|
7001
|
+
try:
|
|
7002
|
+
mpi = item.GetMediaPoolItem()
|
|
7003
|
+
fp = mpi.GetClipProperty("File Path") if mpi else None
|
|
7004
|
+
if fp:
|
|
7005
|
+
actual.add(str(fp))
|
|
7006
|
+
except Exception:
|
|
7007
|
+
pass
|
|
7008
|
+
missing = sorted(x for x in expected_paths if x not in actual)
|
|
7009
|
+
return {"expected": sorted(expected_paths), "actual": sorted(actual), "missing": missing}
|
|
7010
|
+
|
|
7011
|
+
|
|
6969
7012
|
def _timeline_media_coverage(tl) -> Dict[str, Any]:
|
|
6970
7013
|
"""Count how many timeline items are linked to a Media Pool Item vs. offline.
|
|
6971
7014
|
|
|
@@ -7427,6 +7470,20 @@ def _import_timeline_checked(proj, mp, p: Dict[str, Any]):
|
|
|
7427
7470
|
out["relink"] = relink_result
|
|
7428
7471
|
if binary_relink_note:
|
|
7429
7472
|
out["note"] = binary_relink_note
|
|
7473
|
+
if ext in {".drt", ".drp"}:
|
|
7474
|
+
expected_paths = _drt_expected_media_paths(path)
|
|
7475
|
+
if expected_paths:
|
|
7476
|
+
xcheck = _timeline_cross_link_check(imported, expected_paths)
|
|
7477
|
+
if xcheck["missing"]:
|
|
7478
|
+
out["cross_link_warning"] = (
|
|
7479
|
+
"Media files this timeline references are NOT among the files its "
|
|
7480
|
+
f"items actually link to: {', '.join(xcheck['missing'])}. Resolve "
|
|
7481
|
+
"merges pool media by a coarse identity across imports, so clips "
|
|
7482
|
+
"can silently relink to a DIFFERENT pre-existing file (item names "
|
|
7483
|
+
"follow the wrong file too). Import into a fresh project, or "
|
|
7484
|
+
"render-probe before trusting this conform."
|
|
7485
|
+
)
|
|
7486
|
+
out["cross_link_check"] = xcheck
|
|
7430
7487
|
if media["total"] and media["offline"]:
|
|
7431
7488
|
msg = f"{media['offline']} of {media['total']} timeline items are offline (no linked media)."
|
|
7432
7489
|
if is_binary:
|