davinci-resolve-mcp 2.121.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 +8 -2
- package/docs/reference/api-limitations.md +15 -1
- package/install.py +1 -1
- package/package.json +1 -1
- package/resolve-advanced/vendor/drp-format/cut-media.js +8 -6
- package/src/granular/common.py +1 -1
- package/src/server.py +58 -1
- package/src/utils/api_truth.py +49 -0
|
@@ -50,10 +50,13 @@ window. `render.verify_output` covers the container-level checks.
|
|
|
50
50
|
| Custom start timecode | `spec.startFrame` / `preserveStartTimecode` | v2.117 |
|
|
51
51
|
| Timeline markers | `spec.markers` (16 colors, notes, durations, customData) | v2.118 |
|
|
52
52
|
| Turnover markers | EDL `* LOC:` locators + OTIO markers → authored | v2.119 |
|
|
53
|
+
| TC-bearing sources (AAF route) | native clip capture (`MediaStartTime`), channel-leg merge | v2.120–2.122 |
|
|
53
54
|
| Fusion titles | `elements: [{type:'title', text}]` — **21-gen hosts only** | v2.108 |
|
|
54
55
|
|
|
55
56
|
`assemble_from_interchange` drives the same engine from an EDL / OTIO /
|
|
56
|
-
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
|
|
57
60
|
(`authoredTransitions`, `droppedTransitions` with reasons, `authoredRetimes`,
|
|
58
61
|
`flattenedRetimes`, `authoredAudioEvents`, `upperTrackCutsVideoOnly`).
|
|
59
62
|
|
|
@@ -98,7 +101,10 @@ Everything else stays in `droppedTransitions` with the reason.
|
|
|
98
101
|
## Verification checklist for a delivered .drt
|
|
99
102
|
|
|
100
103
|
1. `timeline.import_timeline_checked` — expect `linked == total` for media
|
|
101
|
-
(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.
|
|
102
108
|
2. Render a probe range; check frame luma at cut boundaries, dissolve
|
|
103
109
|
midpoints (expect the blend average), and retime windows.
|
|
104
110
|
3. For audio: RMS per window (silence = -inf is a failed placement).
|
|
@@ -12,7 +12,7 @@ that none exists).
|
|
|
12
12
|
|
|
13
13
|
**Verified on:** DaVinci Resolve Studio 21.0.2
|
|
14
14
|
|
|
15
|
-
**Totals:**
|
|
15
|
+
**Totals:** 35 missing capabilities, 45 bugs / unreliable behaviors.
|
|
16
16
|
|
|
17
17
|
The authoritative source is the runtime-queryable `api_truth` ledger
|
|
18
18
|
(`resolve_control api_truth "<query>"`); this document is generated from
|
|
@@ -221,6 +221,13 @@ equivalent, blocking full automation.
|
|
|
221
221
|
- **Workaround / current handling:** To author a non-default start TC offline, patch MediaExtents (drt.assemble spec.startFrame does this) and keep clip Start frames >= the new origin - clips before it are silently dropped on import. For conform, assemble_from_interchange preserveStartTimecode=true anchors at the turnover's real first record frame instead of 01:00:00:00.
|
|
222
222
|
- **Tags:** timecode, drt, import
|
|
223
223
|
|
|
224
|
+
### Embedded source timecode lives in the clip's MediaStartTime (SECONDS); AAF duplicates audio per channel
|
|
225
|
+
|
|
226
|
+
- **Object:** `Sm2TiVideoClip.MediaStartTime / AAF export`
|
|
227
|
+
- **Behavior:** Two conform-ingest measurements (2026-08-30, rich Resolve 19 AAF export + its sources). (1) A source with embedded timecode is referenced by the timeline clip's <MediaStartTime> in SECONDS (01:00:00:00 -> 3600); a transplant clone keeping the template donor's 0 imports and reads back fine but the render fails with 'Full resolution media not found at 01:00:00:00'. (2) The AAF export carries one event per audio CHANNEL: every A-track event of a dual-mono clip arrives twice with identical ranges.
|
|
228
|
+
- **Workaround / current handling:** capture_media_template harvests mediaStartTime and the native clip elements; drt.assemble clones the source's own captured clip per cut (render-verified: the TC-bearing source plays picture and audio, and the full AAF route renders frame-accurately). Re-capture templates for TC-bearing media. The assemble bridge merges identical audio channel legs (report.audioChannelLegsMerged) instead of refusing them as a same-track overlap.
|
|
229
|
+
- **Tags:** timecode, aaf, audio, drt, silent-failure
|
|
230
|
+
|
|
224
231
|
### MediaPool.ImportMedia (current-folder destination only)
|
|
225
232
|
|
|
226
233
|
- **Object:** `MediaPool`
|
|
@@ -533,6 +540,13 @@ values, or automation-hostile modal prompts.
|
|
|
533
540
|
- **Reference:** [issue #171](https://github.com/samuelgursky/davinci-resolve-mcp/issues/171)
|
|
534
541
|
- **Tags:** timeline, import, silent-failure, unreliable-return
|
|
535
542
|
|
|
543
|
+
### Timeline import MERGES pool media by a coarse identity - similar files can silently cross-link
|
|
544
|
+
|
|
545
|
+
- **Object:** `ImportTimelineFromFile / media pool`
|
|
546
|
+
- **Behavior:** When a .drt import lands in a project that already holds a media entry whose pool identity blob matches the incoming one, Resolve MERGES them and every clip relinks to the EXISTING media - silently. The identity is coarse: two different files (5.6MB vs 93KB, different names, mtimes 1s apart) had identity blobs byte-identical except internal uuids, and the second file's clips all played the first file's picture (readback showed the wrong clip NAME; measured E33/E34 on 19.1.3.7). Importing the same archive into a FRESH project materialized both files correctly - the merge only bites across imports.
|
|
547
|
+
- **Workaround / current handling:** After importing an authored timeline into a non-empty project, verify per-item file paths (or render probe frames) before trusting the conform - linked==total cannot see a cross-link. Files generated in the same second are the risk class; distinct mtimes distinguish them.
|
|
548
|
+
- **Tags:** import, media-pool, silent-failure, drt
|
|
549
|
+
|
|
536
550
|
### Audio tracks cannot be grown in an imported timeline; Fairlight strips live in the pool Sm2Sequence.FieldsBlob
|
|
537
551
|
|
|
538
552
|
- **Object:** `Sm2TiTrack (audio) / FLStudioModelBA`
|
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
|
@@ -78,12 +78,14 @@ async function cutSourceIntoClips(drpInput, opts = {}) {
|
|
|
78
78
|
// mirror clones AUDIO even for plain video cuts, so the donor must be
|
|
79
79
|
// chosen per track type, never per cut flavor (a video element in an
|
|
80
80
|
// audio track aborts the whole import, measured E31e).
|
|
81
|
-
// NATIVE-DONOR PATH
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
//
|
|
81
|
+
// NATIVE-DONOR PATH — RENDER-VERIFIED on 19.1.3.7 (E36): a TC-bearing
|
|
82
|
+
// .mov source (embedded 01:00:00:00) renders picture AND audio through a
|
|
83
|
+
// cloned native clip, and the full AAF route (two sources, V2 stacking,
|
|
84
|
+
// merged audio legs) renders every window frame-accurately. Only caches
|
|
85
|
+
// that carry videoClipElement/audioClipElement reach this; ordinary
|
|
86
|
+
// captures keep the proven donor path. Donor clones from
|
|
87
|
+
// splitClipElements come <Element>-wrapped and Items concatenation
|
|
88
|
+
// depends on the wrapper.
|
|
87
89
|
const native = kind === 'audio' ? cut.donorClipAudio : cut.donorClipVideo;
|
|
88
90
|
let c = freshDbIds(native ? native.trim() : donor);
|
|
89
91
|
c = setClipStart(c, cut.startFrame);
|
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:
|
package/src/utils/api_truth.py
CHANGED
|
@@ -1936,6 +1936,55 @@ API_TRUTH: List[Dict[str, Any]] = [
|
|
|
1936
1936
|
"tags": ["timecode", "drt", "import"],
|
|
1937
1937
|
"submit": "missing",
|
|
1938
1938
|
},
|
|
1939
|
+
{
|
|
1940
|
+
"symbol": "Timeline import MERGES pool media by a coarse identity - similar files can silently cross-link",
|
|
1941
|
+
"object": "ImportTimelineFromFile / media pool",
|
|
1942
|
+
"reality": "When a .drt import lands in a project that already "
|
|
1943
|
+
"holds a media entry whose pool identity blob matches "
|
|
1944
|
+
"the incoming one, Resolve MERGES them and every clip "
|
|
1945
|
+
"relinks to the EXISTING media - silently. The identity "
|
|
1946
|
+
"is coarse: two different files (5.6MB vs 93KB, "
|
|
1947
|
+
"different names, mtimes 1s apart) had identity blobs "
|
|
1948
|
+
"byte-identical except internal uuids, and the second "
|
|
1949
|
+
"file's clips all played the first file's picture "
|
|
1950
|
+
"(readback showed the wrong clip NAME; measured E33/E34 "
|
|
1951
|
+
"on 19.1.3.7). Importing the same archive into a FRESH "
|
|
1952
|
+
"project materialized both files correctly - the merge "
|
|
1953
|
+
"only bites across imports.",
|
|
1954
|
+
"recommended": "After importing an authored timeline into a "
|
|
1955
|
+
"non-empty project, verify per-item file paths (or "
|
|
1956
|
+
"render probe frames) before trusting the conform - "
|
|
1957
|
+
"linked==total cannot see a cross-link. Files "
|
|
1958
|
+
"generated in the same second are the risk class; "
|
|
1959
|
+
"distinct mtimes distinguish them.",
|
|
1960
|
+
"tags": ["import", "media-pool", "silent-failure", "drt"],
|
|
1961
|
+
"submit": "bug",
|
|
1962
|
+
},
|
|
1963
|
+
{
|
|
1964
|
+
"symbol": "Embedded source timecode lives in the clip's MediaStartTime (SECONDS); AAF duplicates audio per channel",
|
|
1965
|
+
"object": "Sm2TiVideoClip.MediaStartTime / AAF export",
|
|
1966
|
+
"reality": "Two conform-ingest measurements (2026-08-30, rich "
|
|
1967
|
+
"Resolve 19 AAF export + its sources). (1) A source with "
|
|
1968
|
+
"embedded timecode is referenced by the timeline clip's "
|
|
1969
|
+
"<MediaStartTime> in SECONDS (01:00:00:00 -> 3600); a "
|
|
1970
|
+
"transplant clone keeping the template donor's 0 imports "
|
|
1971
|
+
"and reads back fine but the render fails with 'Full "
|
|
1972
|
+
"resolution media not found at 01:00:00:00'. (2) The AAF "
|
|
1973
|
+
"export carries one event per audio CHANNEL: every "
|
|
1974
|
+
"A-track event of a dual-mono clip arrives twice with "
|
|
1975
|
+
"identical ranges.",
|
|
1976
|
+
"recommended": "capture_media_template harvests mediaStartTime and "
|
|
1977
|
+
"the native clip elements; drt.assemble clones the "
|
|
1978
|
+
"source's own captured clip per cut (render-verified: "
|
|
1979
|
+
"the TC-bearing source plays picture and audio, and "
|
|
1980
|
+
"the full AAF route renders frame-accurately). "
|
|
1981
|
+
"Re-capture templates for TC-bearing media. The "
|
|
1982
|
+
"assemble bridge merges identical audio channel legs "
|
|
1983
|
+
"(report.audioChannelLegsMerged) instead of refusing "
|
|
1984
|
+
"them as a same-track overlap.",
|
|
1985
|
+
"tags": ["timecode", "aaf", "audio", "drt", "silent-failure"],
|
|
1986
|
+
"submit": "missing",
|
|
1987
|
+
},
|
|
1939
1988
|
{
|
|
1940
1989
|
"symbol": "Audio tracks cannot be grown in an imported timeline; Fairlight strips live in the pool Sm2Sequence.FieldsBlob",
|
|
1941
1990
|
"object": "Sm2TiTrack (audio) / FLStudioModelBA",
|