davinci-resolve-mcp 2.80.0 → 2.80.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 +120 -0
- package/README.md +1 -1
- package/docs/README.md +78 -9
- package/docs/kernels/audio-fairlight-kernel.md +1 -1
- package/docs/kernels/color-grade-kernel.md +1 -1
- package/docs/kernels/fusion-composition-kernel.md +1 -1
- package/docs/kernels/media-pool-ingest-kernel.md +1 -1
- package/docs/kernels/render-deliver-kernel.md +1 -1
- package/docs/kernels/timeline-conform-interchange-kernel.md +1 -1
- package/docs/kernels/timeline-edit-kernel.md +1 -1
- package/docs/reference/api-limitations.md +3 -3
- package/install.py +1 -1
- package/package.json +1 -1
- package/scripts/agent-rules/README.md +7 -4
- package/src/granular/common.py +1 -1
- package/src/server.py +67 -6
- package/src/utils/api_truth.py +83 -23
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,126 @@
|
|
|
2
2
|
|
|
3
3
|
Release history for the DaVinci Resolve MCP Server. The latest release is summarized in the root README; older entries live here to keep the README focused.
|
|
4
4
|
|
|
5
|
+
## What's New in v2.80.2
|
|
6
|
+
|
|
7
|
+
Agent tooling only. Ten Claude Code skills that this repository has shipped and
|
|
8
|
+
advertised were never loading; they load now. No runtime behavior changed and
|
|
9
|
+
nothing under `src/` was touched.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **The ten `.claude/skills/` domain skills were invisible to every agent.**
|
|
14
|
+
Claude Code discovers skills at `.claude/skills/<name>/SKILL.md`. All ten were
|
|
15
|
+
loose `.md` files at the top level of that directory — a layout the loader
|
|
16
|
+
does not scan — so `resolve-color`, `resolve-edit`, `resolve-conform`,
|
|
17
|
+
`resolve-delivery`, `resolve-audio`, `resolve-fusion`, `resolve-media-pool`,
|
|
18
|
+
`resolve-media-analysis`, `resolve-rough-cut`, and `resolve-mcp` never
|
|
19
|
+
appeared in a session, while the generated domain-routing block in `AGENTS.md`
|
|
20
|
+
and the index in `docs/README.md` both listed them as available. The failure
|
|
21
|
+
was silent: no warning, no error, no degraded mode. Each skill now lives in a
|
|
22
|
+
directory named for its frontmatter `name` (recorded as 100% renames, so
|
|
23
|
+
history follows), and both `docs/README.md` and
|
|
24
|
+
`scripts/agent-rules/README.md` — the file consulted when authoring a new
|
|
25
|
+
skill — state the directory requirement so the next one is not written back
|
|
26
|
+
into the bug.
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- **Two opt-in `PreToolUse` guard scripts** for rules `AGENTS.md` has only ever
|
|
31
|
+
stated in prose. They ship as scripts and are deliberately *not* wired
|
|
32
|
+
repo-wide; `docs/README.md` carries the block to paste into a personal
|
|
33
|
+
gitignored `.claude/settings.local.json`. `frame_verification_guard.py`
|
|
34
|
+
refuses grade-applying actions on `timeline_item_color` until the session has
|
|
35
|
+
actually looked at a Resolve-rendered frame, and asks before `safe_copy_grade`
|
|
36
|
+
/ `bulk_match_to_hero` push a whole-grade artifact across clips; `dry_run`
|
|
37
|
+
passes through. `source_media_guard.py` refuses shell commands that write,
|
|
38
|
+
move, or delete source media outside a scratch root — paths are normalized and
|
|
39
|
+
matched by whole path component, and a derivative-output directory
|
|
40
|
+
(`proxies`/`renders`/`exports`) exempts a *write* but never a delete or a
|
|
41
|
+
move, so a camera card with an `exports` folder is still a camera card. Its
|
|
42
|
+
docstring states what it cannot catch — extension-less directory deletes,
|
|
43
|
+
`find -delete`, `xargs rm`, and scripts that write media themselves — because
|
|
44
|
+
it is a tripwire for the common direct mistake, not a sandbox.
|
|
45
|
+
- **Two review subagents** in `.claude/agents/`, run in their own context so
|
|
46
|
+
frame images stay out of the main session. `cut-reviewer` screens an assembled
|
|
47
|
+
timeline from its frames and is told explicitly that a metadata summary is not
|
|
48
|
+
a review, because assembling through an API succeeds loudly and fails quietly.
|
|
49
|
+
`grade-match-verifier` measures shot match numerically against the project's
|
|
50
|
+
R−B tolerance and must report the pixel count behind every masked
|
|
51
|
+
measurement — a near-empty skin mask returns a delta near zero and reads as a
|
|
52
|
+
perfect match.
|
|
53
|
+
- **Two skills outside the domain routing.** `house-style` accumulates editorial
|
|
54
|
+
corrections so the same note is not given twice, and `/resolve-session`
|
|
55
|
+
reports connection, edition, project, timeline, and media-pool state before
|
|
56
|
+
editing begins.
|
|
57
|
+
|
|
58
|
+
### Validation
|
|
59
|
+
|
|
60
|
+
- Full offline suite: 2460 passed, 1 skipped — level with the v2.80.1 baseline,
|
|
61
|
+
as expected for a change that touches no runtime code.
|
|
62
|
+
- Both guard scripts exercised against a 26-case matrix covering deny, ask, and
|
|
63
|
+
silent-allow: `ffprobe` reads, `ffmpeg` into scratch, `ffmpeg` overwriting a
|
|
64
|
+
card, chained `ffprobe && rm`, redirection onto a media file, glob `rm`,
|
|
65
|
+
quoted paths, writes into `renders`/`proxies`/`exports`, deletes out of those
|
|
66
|
+
same directories, `..` traversal, and ordinary repo commands (`git status`,
|
|
67
|
+
the test runner, `npm run build`).
|
|
68
|
+
- No live Resolve validation: no behavior changed.
|
|
69
|
+
|
|
70
|
+
## What's New in v2.80.1
|
|
71
|
+
|
|
72
|
+
A correction to the retime measurement contract published in v2.80.0, and a fix
|
|
73
|
+
for a documented timecode conversion that never happened.
|
|
74
|
+
|
|
75
|
+
### Fixed
|
|
76
|
+
|
|
77
|
+
- **`timeline_markers.set_current_timecode` now honors the documented
|
|
78
|
+
elapsed-timecode conversion.** The tool doc has always said timecodes before
|
|
79
|
+
the timeline start are treated as elapsed time and converted automatically —
|
|
80
|
+
but only marker actions did the conversion. `set_current_timecode` passed the
|
|
81
|
+
raw string to `Timeline.SetCurrentTimecode`, which refuses sub-start
|
|
82
|
+
timecodes with a bare `False` and no error info (measured on Studio 19.1.3.7:
|
|
83
|
+
on a timeline starting `00:59:50:00`, `00:00:21:03` failed while
|
|
84
|
+
`01:00:11:03` succeeded). The wrapper now lifts elapsed timecodes by the
|
|
85
|
+
start frame — `00:00:21:03` lands the playhead at `01:00:11:03` — with
|
|
86
|
+
drop-frame-correct formatting on DF timelines. Absolute timecodes and strings
|
|
87
|
+
the parser cannot read pass through unchanged. Marker `add()`'s conversion
|
|
88
|
+
was re-verified live on a non-zero-start timeline (elapsed `00:00:21:03` →
|
|
89
|
+
relative frame 507) and was already correct.
|
|
90
|
+
|
|
91
|
+
### Documentation
|
|
92
|
+
|
|
93
|
+
- **🚩 Correction to v2.80.0's retime witness — the recommended instrument
|
|
94
|
+
cannot see retimes.** The v2.80.0 retime entry recommended reading
|
|
95
|
+
`GetLeftOffset`/`GetRightOffset` to tell whether a retime was built. A
|
|
96
|
+
calibration with the confound removed (the SAME clip twice in ONE timeline,
|
|
97
|
+
one copy hand-set to 200%, Studio 19.1.3.7) proves that pair reads the
|
|
98
|
+
WARPED domain — position ÷ speed, span always equal to the record span — so
|
|
99
|
+
it is exact for placement and structurally blind for speed. The corrected
|
|
100
|
+
entry installs the calibrated model: judge speed by the
|
|
101
|
+
`GetSourceStartFrame`/`GetSourceEndFrame` span vs the record duration (the
|
|
102
|
+
200% copy read span 96 vs 48; a 0/0 read on xmeml-imported timelines is
|
|
103
|
+
UNKNOWN, never "no retime"), cross-checked by the `Sm2TimeMap` slope in a
|
|
104
|
+
saved Project.db or the `EXPORT_EDL` M2 rate.
|
|
105
|
+
- **Two import routes DO build constant retimes**, now documented with their
|
|
106
|
+
emission rules: OTIO `LinearTimeWarp` through `ImportTimelineFromFile` (200%
|
|
107
|
+
and 50% measured; `source_range.duration` is the RECORD span — the
|
|
108
|
+
`time_scalar` handles source consumption; source frames timecode-absolute)
|
|
109
|
+
and EDL `M2` in the exact shape Resolve's own `EXPORT_EDL` writes (200%
|
|
110
|
+
measured; event-line source span equals the record span; `* FROM CLIP NAME:`
|
|
111
|
+
drives linking). Reverse and varying-speed maps remain untested as import
|
|
112
|
+
routes and the entry says so.
|
|
113
|
+
- `docs/reference/api-limitations.md` regenerated; the `GetSourceStartFrame`
|
|
114
|
+
off-by-one entry now scopes its GetLeftOffset advice to 100%-speed placement.
|
|
115
|
+
|
|
116
|
+
### Validation
|
|
117
|
+
|
|
118
|
+
- Full offline suite: 2460 passed, 1 skipped (up exactly the 7 new tests from
|
|
119
|
+
the 2453 baseline).
|
|
120
|
+
- Live Resolve Studio 19.1.3.7: new
|
|
121
|
+
`tests/live_playhead_timecode_validation.py` harness — raw refusal control,
|
|
122
|
+
elapsed lift to `01:00:11:03`, absolute pass-through, and marker add() at
|
|
123
|
+
relative frame 507 all verified against a disposable project.
|
|
124
|
+
|
|
5
125
|
## What's New in v2.80.0
|
|
6
126
|
|
|
7
127
|
Three community PRs from @staahlarkitektur, all found on Windows, all real. Each is merged with
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# DaVinci Resolve MCP Server
|
|
2
2
|
|
|
3
|
-
[](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
|
|
4
4
|
[](https://www.npmjs.com/package/davinci-resolve-mcp)
|
|
5
5
|
[](docs/reference/api-coverage.md)
|
|
6
6
|
[-blue.svg)](#server-modes)
|
package/docs/README.md
CHANGED
|
@@ -47,19 +47,88 @@ Per-domain skills in `.claude/skills/` route craft ↔ live tools ↔ offline
|
|
|
47
47
|
advanced tools automatically when an agent works in that domain. They are thin
|
|
48
48
|
bridges — the authoritative depth stays in the kernels and guides above.
|
|
49
49
|
|
|
50
|
-
- `resolve-mcp` (`.claude/skills/resolve.md`) — orientation/index: the map to the domain skills below (self-trigger; not an auto-loader)
|
|
51
|
-
- `resolve-color` (`.claude/skills/color
|
|
52
|
-
- `resolve-edit` (`.claude/skills/
|
|
53
|
-
- `resolve-conform` (`.claude/skills/conform.md`) — conform, relink, finishing QC, grade tracing
|
|
54
|
-
- `resolve-delivery` (`.claude/skills/delivery.md`) — render, deliverable QC, media/provenance
|
|
55
|
-
- `resolve-fusion` (`.claude/skills/fusion.md`) — Fusion comps (titles, motion graphics, VFX)
|
|
56
|
-
- `resolve-audio` (`.claude/skills/audio.md`) — audio/Fairlight tracks, buses, loudness, sync
|
|
57
|
-
- `resolve-media-pool` (`.claude/skills/media-pool.md`) — media pool ingest, organize, multicam
|
|
58
|
-
- `resolve-media-analysis` (`.claude/skills/media-analysis.md`) — source-safe media intelligence
|
|
50
|
+
- `resolve-mcp` (`.claude/skills/resolve-mcp/SKILL.md`) — orientation/index: the map to the domain skills below (self-trigger; not an auto-loader)
|
|
51
|
+
- `resolve-color` (`.claude/skills/resolve-color/SKILL.md`) — grading, looks, shot match, LUT/CDL/DRX
|
|
52
|
+
- `resolve-edit` (`.claude/skills/resolve-edit/SKILL.md`) — cutting, ranges, variants, changelist
|
|
53
|
+
- `resolve-conform` (`.claude/skills/resolve-conform/SKILL.md`) — conform, relink, finishing QC, grade tracing
|
|
54
|
+
- `resolve-delivery` (`.claude/skills/resolve-delivery/SKILL.md`) — render, deliverable QC, media/provenance
|
|
55
|
+
- `resolve-fusion` (`.claude/skills/resolve-fusion/SKILL.md`) — Fusion comps (titles, motion graphics, VFX)
|
|
56
|
+
- `resolve-audio` (`.claude/skills/resolve-audio/SKILL.md`) — audio/Fairlight tracks, buses, loudness, sync
|
|
57
|
+
- `resolve-media-pool` (`.claude/skills/resolve-media-pool/SKILL.md`) — media pool ingest, organize, multicam
|
|
58
|
+
- `resolve-media-analysis` (`.claude/skills/resolve-media-analysis/SKILL.md`) — source-safe media intelligence
|
|
59
|
+
|
|
60
|
+
Each skill is a directory containing `SKILL.md`. Claude Code does not discover
|
|
61
|
+
loose `.md` files in `.claude/skills/`; a skill placed at the top level of that
|
|
62
|
+
directory silently never loads.
|
|
63
|
+
|
|
64
|
+
Two skills sit outside the domain routing:
|
|
65
|
+
|
|
66
|
+
- `house-style` (`.claude/skills/house-style/SKILL.md`) — accumulated editorial
|
|
67
|
+
corrections, so the same note is not given twice. Claude-only; append to it
|
|
68
|
+
when an editorial decision is corrected.
|
|
69
|
+
- `resolve-session` (`.claude/skills/resolve-session/SKILL.md`) — `/resolve-session`
|
|
70
|
+
connects, confirms edition and bridge, and reports project/timeline/pool state.
|
|
59
71
|
|
|
60
72
|
The offline half of every one is the advanced server; see
|
|
61
73
|
[Advanced Server](../resolve-advanced/README.md).
|
|
62
74
|
|
|
75
|
+
## Claude Code Hooks and Subagents
|
|
76
|
+
|
|
77
|
+
Two `PreToolUse` guards enforce rules `AGENTS.md` states in prose. They ship as
|
|
78
|
+
scripts but are **not** wired up by default — the repository does not enable
|
|
79
|
+
hooks on your behalf. Opt in by adding the block below to your own
|
|
80
|
+
`.claude/settings.local.json` (gitignored, so it stays yours):
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"hooks": {
|
|
85
|
+
"PreToolUse": [
|
|
86
|
+
{
|
|
87
|
+
"matcher": "mcp__davinci-resolve__(timeline_item_color|color_group)",
|
|
88
|
+
"hooks": [
|
|
89
|
+
{
|
|
90
|
+
"type": "command",
|
|
91
|
+
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/frame_verification_guard.py",
|
|
92
|
+
"timeout": 15
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"matcher": "Bash",
|
|
98
|
+
"hooks": [
|
|
99
|
+
{
|
|
100
|
+
"type": "command",
|
|
101
|
+
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/source_media_guard.py",
|
|
102
|
+
"timeout": 15
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
}
|
|
106
|
+
]
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Hooks are read at session start, so restart Claude Code after adding them. The
|
|
112
|
+
two guards are:
|
|
113
|
+
|
|
114
|
+
- `.claude/hooks/frame_verification_guard.py` — denies grade-applying actions on
|
|
115
|
+
`timeline_item_color` until the session has actually looked at a
|
|
116
|
+
Resolve-rendered frame, and asks before whole-grade artifacts
|
|
117
|
+
(`safe_copy_grade`, `bulk_match_to_hero`) overwrite hand-work. `dry_run`
|
|
118
|
+
passes through untouched.
|
|
119
|
+
- `.claude/hooks/source_media_guard.py` — denies shell commands that write,
|
|
120
|
+
move, or delete source media outside a scratch root. Reads (`ffprobe`, and
|
|
121
|
+
`ffmpeg` writing into scratch) pass.
|
|
122
|
+
|
|
123
|
+
Two review subagents in `.claude/agents/` run in their own context so frame
|
|
124
|
+
images stay out of the main session:
|
|
125
|
+
|
|
126
|
+
- `cut-reviewer` — screens an assembled timeline from its frames and reports on
|
|
127
|
+
pacing, shot order, continuity, and coverage gaps.
|
|
128
|
+
- `grade-match-verifier` — measures shot match numerically from rendered frames
|
|
129
|
+
against the project's R−B tolerance, and reports mask pixel counts so an empty
|
|
130
|
+
skin mask cannot pass as a match.
|
|
131
|
+
|
|
63
132
|
## Authoring References
|
|
64
133
|
|
|
65
134
|
- [Fuse + DCTL Authoring](authoring/fuse-dctl-authoring.md)
|
|
@@ -88,7 +88,7 @@ patches audio **offline, no Resolve running**:
|
|
|
88
88
|
|
|
89
89
|
Rule of thumb: plan/measure offline, apply mix/track changes live; use
|
|
90
90
|
`fairlight` for bus work the scripting API can't reach. See the `resolve-audio`
|
|
91
|
-
skill (`.claude/skills/audio.md`) and the `/audio_workflow` prompt.
|
|
91
|
+
skill (`.claude/skills/resolve-audio/SKILL.md`) and the `/audio_workflow` prompt.
|
|
92
92
|
|
|
93
93
|
## Live Probe
|
|
94
94
|
|
|
@@ -134,7 +134,7 @@ Cross-server rules an agent must know:
|
|
|
134
134
|
- **Deps.** The grading catalog needs `sharp`; call the advanced `capabilities`
|
|
135
135
|
tool for live status and install hints.
|
|
136
136
|
|
|
137
|
-
See the `resolve-color` skill (`.claude/skills/color
|
|
137
|
+
See the `resolve-color` skill (`.claude/skills/resolve-color/SKILL.md`) for the
|
|
138
138
|
craft ↔ live ↔ offline routing and the frame-first rule.
|
|
139
139
|
|
|
140
140
|
## Live Probe
|
|
@@ -119,7 +119,7 @@ running**, via the `fusion` tool:
|
|
|
119
119
|
Rule of thumb: author and verify the comp offline, then apply it live with
|
|
120
120
|
`fusion_comp` `safe_add_tool` → `safe_set_inputs` → `safe_connect_tools` (the
|
|
121
121
|
`to_api_calls` output maps directly onto those). See the `resolve-fusion` skill
|
|
122
|
-
(`.claude/skills/fusion.md`) and the `/fusion_workflow` prompt.
|
|
122
|
+
(`.claude/skills/resolve-fusion/SKILL.md`) and the `/fusion_workflow` prompt.
|
|
123
123
|
|
|
124
124
|
## Live Probe
|
|
125
125
|
|
|
@@ -137,7 +137,7 @@ actions):
|
|
|
137
137
|
Rule of thumb: verify and inventory the card offline *before* importing, then
|
|
138
138
|
import/organize live. `media` also serves the delivery side (see the
|
|
139
139
|
`resolve-delivery` skill). See the `resolve-media-pool` skill
|
|
140
|
-
(`.claude/skills/media-pool.md`) and the `/media_pool_workflow` prompt. Never
|
|
140
|
+
(`.claude/skills/resolve-media-pool/SKILL.md`) and the `/media_pool_workflow` prompt. Never
|
|
141
141
|
rename or derive camera originals without explicit approval.
|
|
142
142
|
|
|
143
143
|
## Live Evidence
|
|
@@ -135,7 +135,7 @@ Rules an agent must know:
|
|
|
135
135
|
- **Deps.** `deliverable`/`media` QC needs **ffmpeg + ffprobe on PATH** (GPL, not
|
|
136
136
|
bundled) — call the advanced `capabilities` tool for status + install hints.
|
|
137
137
|
|
|
138
|
-
See the `resolve-delivery` skill (`.claude/skills/delivery.md`) for the
|
|
138
|
+
See the `resolve-delivery` skill (`.claude/skills/resolve-delivery/SKILL.md`) for the
|
|
139
139
|
craft ↔ live ↔ offline routing.
|
|
140
140
|
|
|
141
141
|
## Live Evidence
|
|
@@ -121,7 +121,7 @@ Gotchas the live path shares:
|
|
|
121
121
|
- **Deps.** `better-sqlite3` gates lineage/reverse/DB; `sharp`/ffmpeg gate frame
|
|
122
122
|
compare — call the advanced `capabilities` tool.
|
|
123
123
|
|
|
124
|
-
See the `resolve-conform` skill (`.claude/skills/conform.md`) for the
|
|
124
|
+
See the `resolve-conform` skill (`.claude/skills/resolve-conform/SKILL.md`) for the
|
|
125
125
|
craft ↔ live ↔ offline routing.
|
|
126
126
|
|
|
127
127
|
## Live Probe
|
|
@@ -196,7 +196,7 @@ Use these to answer "what changed between v3 and v4" or to hand a conform an
|
|
|
196
196
|
accurate change list without opening either timeline. For conforming/relinking
|
|
197
197
|
that change list, see the Timeline Conform / Interchange kernel and the
|
|
198
198
|
`resolve-conform` skill; for the edit ↔ offline routing, see the `resolve-edit`
|
|
199
|
-
skill (`.claude/skills/
|
|
199
|
+
skill (`.claude/skills/resolve-edit/SKILL.md`).
|
|
200
200
|
|
|
201
201
|
## Development Guardrails
|
|
202
202
|
|
|
@@ -117,8 +117,8 @@ equivalent, blocking full automation.
|
|
|
117
117
|
### Clip speed / retime ratio and speed ramps
|
|
118
118
|
|
|
119
119
|
- **Object:** `TimelineItem`
|
|
120
|
-
- **Behavior:** SetProperty exposes only retime *quality* (RetimeProcess, MotionEstimation) and transform/crop/composite/opacity keys — not the speed value itself. There is no way to set a clip to a given % speed, reverse it, or author a speed ramp. Verified against the documented SetProperty key list AND by live mutating attempt on 21.0.0: SetProperty('Speed'|'PlaybackSpeed'|'RetimeSpeed'|'ClipSpeed', 50) all return False, while SetProperty('RetimeProcess', 1) returns True. THE READ SIDE IS AS DEAD AS THE WRITE SIDE, which is easy to miss: re-measured on Studio 19.1.3.7 against a placed item, GetProperty('Speed'), GetProperty('PlaybackSpeed'), GetProperty('RetimeSpeed') and GetProperty('ClipSpeed') ALL return None, and the keyless GetProperty() dict (26 keys on that item) carries no speed value at all — its only retime key is RetimeProcess, which is quality, not ratio. SetProperty('Speed', 1.75) returned False on 19.1.3.7 too, so the write refusal is not specific to 21.0.0. Note the 21.0.0 stamp above covers the SetProperty measurements only. THE SCRIPTING-API xmeml IMPORT BUILDS NO RETIME — and the way it fails is worse than a no-op. First, what Premiere actually writes, because having this backwards is what produced the wrong contract this entry published in 2.79.0–2.79.1 (see CORRECTION below). In an FCP7 XML a retimed clipitem's <in>/<out> live in the POST-RETIME (warped) domain and always span the RECORD duration; pproTicksIn/pproTicksOut carry the TRUE SOURCE position; and <duration> is the file length expressed in the warped domain. A real 200% clip at 24 fps: <in>1957</in> <out>1971</out> — span 14, EQUAL to its record span; pproTicksIn 41425776000000 and pproTicksOut 41722128000000, which at 254016000000/24 = 10584000000 ticks per frame are source frames 3914 and 3942, exactly 1957x2 and 1971x2, a 28-frame source span over a 14-frame record span; <duration>24292</duration> for a 48584-frame file; and a graphdict mapping warped to true source with the ratio as its slope (when 17910 -> value 35820). So for a retimed clip <in> and pproTicksIn are SUPPOSED to disagree, by exactly the ratio. The same relationship seen from the other side is already encoded in this repo: resolve-advanced/server/prproj.mjs derives Premiere speed from tick geometry as |srcSpan / recSpan| * 100, reversing when in > out. Against that convention, measured on 19.1.3/19.1.3.7: (a) the importer IGNORES the scalar Time Remap speed filter and the clips arrive at 100%; (b) `graphdict` is ignored too — re-tested in Premiere's exact convention with one 100% control clip and one 200% clip per timeline, a document carrying warped <in>/<out>, true-source pproTicks, <duration> = fileLen/ratio and a constant-slope graphdict imports cleanly, the control lands correct, and
|
|
121
|
-
- **Workaround / current handling:** Set clip speed/retime in the Resolve UI
|
|
120
|
+
- **Behavior:** SetProperty exposes only retime *quality* (RetimeProcess, MotionEstimation) and transform/crop/composite/opacity keys — not the speed value itself. There is no way to set a clip to a given % speed, reverse it, or author a speed ramp. Verified against the documented SetProperty key list AND by live mutating attempt on 21.0.0: SetProperty('Speed'|'PlaybackSpeed'|'RetimeSpeed'|'ClipSpeed', 50) all return False, while SetProperty('RetimeProcess', 1) returns True. THE READ SIDE IS AS DEAD AS THE WRITE SIDE, which is easy to miss: re-measured on Studio 19.1.3.7 against a placed item, GetProperty('Speed'), GetProperty('PlaybackSpeed'), GetProperty('RetimeSpeed') and GetProperty('ClipSpeed') ALL return None, and the keyless GetProperty() dict (26 keys on that item) carries no speed value at all — its only retime key is RetimeProcess, which is quality, not ratio. SetProperty('Speed', 1.75) returned False on 19.1.3.7 too, so the write refusal is not specific to 21.0.0. Note the 21.0.0 stamp above covers the SetProperty measurements only. THE SCRIPTING-API xmeml IMPORT BUILDS NO RETIME — and the way it fails is worse than a no-op. First, what Premiere actually writes, because having this backwards is what produced the wrong contract this entry published in 2.79.0–2.79.1 (see CORRECTION below). In an FCP7 XML a retimed clipitem's <in>/<out> live in the POST-RETIME (warped) domain and always span the RECORD duration; pproTicksIn/pproTicksOut carry the TRUE SOURCE position; and <duration> is the file length expressed in the warped domain. A real 200% clip at 24 fps: <in>1957</in> <out>1971</out> — span 14, EQUAL to its record span; pproTicksIn 41425776000000 and pproTicksOut 41722128000000, which at 254016000000/24 = 10584000000 ticks per frame are source frames 3914 and 3942, exactly 1957x2 and 1971x2, a 28-frame source span over a 14-frame record span; <duration>24292</duration> for a 48584-frame file; and a graphdict mapping warped to true source with the ratio as its slope (when 17910 -> value 35820). So for a retimed clip <in> and pproTicksIn are SUPPOSED to disagree, by exactly the ratio. The same relationship seen from the other side is already encoded in this repo: resolve-advanced/server/prproj.mjs derives Premiere speed from tick geometry as |srcSpan / recSpan| * 100, reversing when in > out. Against that convention, measured on 19.1.3/19.1.3.7: (a) the importer IGNORES the scalar Time Remap speed filter and the clips arrive at 100%; (b) `graphdict` is ignored too — re-tested in Premiere's exact convention with one 100% control clip and one 200% clip per timeline, a document carrying warped <in>/<out>, true-source pproTicks, <duration> = fileLen/ratio and a constant-slope graphdict imports cleanly, the control lands correct, and NO retime is built: recalibrated 2026-08-05, every xmeml-imported clip carries a DEGENERATE time map in Project.db (Sm2TimeMap with an empty source axis — five Time Remap shapes re-measured, 15/15 clips degenerate), so no speed exists in the project data, and the API source witness reads 0/0 on those clips (see WITNESS CALIBRATION below); emitting the identical document WITHOUT the graphdict gives the identical result; (c) `reverse` does not survive either; (d) THE HAZARD, and it is the part that bites: Resolve reads <in> LITERALLY as the true source frame, honouring neither the ticks nor the graphdict. Import a genuine Premiere XML that contains retimes and every retimed clip is placed at in / ratio — the 200% clip above lands on source frame 1957 instead of 3914. There is no error, the cut lengths are still correct, every clip is linked and online, and the timeline renders — so it reads as a good conform while sitting at the wrong moment of the right file. This is the same failure class as the Avid AAF camera-file link (docs/guides/conforming-an-avid-aaf.md): wrong in a way only a frame comparison against a reference can see. SCOPE: all of the above is the SCRIPTING-API import (ImportTimelineFromFile). Resolve's UI importer (File > Import > Timeline) has NOT been tested, and that is how editors usually conform a Premiere XML — do not read this as covering it. CORRECTION: this entry as published in 2.79.0–2.79.1 also claimed that any <in>/<pproTicksIn> inconsistency is silently REJECTED in both orientations. That claim was FALSE and has been removed — it came from an emitter writing ticks = in x ticks-per-frame at every speed, so what it observed was its own malformed files being refused. The graphdict evidence published with it (dead in FOUR shapes, 0 of 2 landed, a 200% clip emitted in 200 / out 296 'clamped' to out 248) described that same malformed input being normalized and is replaced by the re-test above. The conclusion is unchanged; only its evidence is. Placement is NOT the problem: the same route imported 573 clips with 572 of 573 matching by track and record position with source frames exact, and the importer BUILT a 59-frame dissolve. The retime gap is specific, not general. TRAP: Resolve's own FCP7 export cannot witness a speed. It writes a DEGENERATE Time Remap on every clip — `speed` value 0 (not 100) and a graphdict whose keyframe `value`s are all 0 while its `when`s carry the clip's source in/out — so anyone verifying a retime by round-tripping through EXPORT_FCP_7_XML is reading furniture, and the identity Time Remap blocks present on every clip are what make the route look like it should work. WITNESS CALIBRATION (2026-08-05, Studio 19.1.3.7) — the positive control this entry previously lacked now exists, and it RETRACTS the witness the 2.80.0 revision of this entry recommended. The rig removed every confound: the SAME clip placed twice, adjacent, in ONE timeline, the second copy hand-set to 200% in the UI (the only way to make one — see above). GetSourceStartFrame/GetSourceEndFrame separated the copies — 1822..1870 (span 48) at 100% vs 1822..1918 (span 96) at 200% — while GetLeftOffset/GetRightOffset did NOT: 1822..1870 at 100% vs 911..959 at 200%, which is exactly position / 2. GetLeftOffset reports the WARPED (record-side) domain — position / speed, the `In` column of Project.db's Sm2TiItem — so it is exact for PLACEMENT and blind for SPEED BY CONSTRUCTION: its span equals the record span at every speed. The speed itself lives in the item's Sm2TimeMap blob (keyframe slope = ratio; the hand-set 200% reads slope exactly 2.0), which is what GetSourceStart/EndFrame and EXPORT_EDL read. THE SPEED WITNESS is therefore the GetSourceStart/EndFrame span vs the record duration. CAVEAT: on xmeml-IMPORTED timelines those return 0/0 — the importer leaves the time map's source axis empty — and a 0/0 read is UNKNOWN, never 'no retime'. Cross-checks that work everywhere: the Sm2TimeMap slope read from a saved Project.db, and the EXPORT_EDL M2 rate (rate = fps x speed/100, so 048.0 = 200% at 24 fps; `M2 ... 000.0` on every clip is the degenerate-map furniture of an xmeml import — ignore it). TWO IMPORT ROUTES DO BUILD CONSTANT RETIMES (measured 2026-08-05, media linked, judged via the calibrated witnesses above): (1) OTIO LinearTimeWarp through ImportTimelineFromFile — 200% (src 200..296 over a 48-frame record) and 50% (src 300..324 over a 48-frame record) both landed with correct source in-points; the saved Project.db shows slope 2.0 and 0.5. Emission rules: the document must be Resolve-shaped with TIMECODE-ABSOLUTE source frames (see the ImportTimelineFromFile .otio entry), the effect is `LinearTimeWarp.1` with `time_scalar`, and `source_range.duration` is the RECORD span — OTIO semantics, the time_scalar handles source consumption; sending the source span as the duration builds a spec-correctly longer clip, not a retime. (2) EDL M2 — 200% landed (src 100..196 over a 48-frame record), linked. Author the shape Resolve's own EXPORT_EDL writes: the event line's source span EQUALS the record span even under M2; the `M2 <reel> <rate> <srcInTC>` line carries the play rate in fps (048.0 = 200% at 24); `* FROM CLIP NAME:` comments drive pool linking. UNTESTED as import routes: reverse (negative time_scalar / reverse M2) and varying-speed maps — do not extrapolate the constant-speed result to them.
|
|
121
|
+
- **Workaround / current handling:** Set clip speed/retime in the Resolve UI, or BUILD it by import: OTIO LinearTimeWarp and EDL M2 both construct constant retimes through ImportTimelineFromFile (measured — emission rules in reality above); xmeml does not, in any Time Remap shape. To READ a retime back, judge speed by the GetSourceStart/EndFrame span vs the record duration — a 0/0 read (xmeml-imported timelines) is UNKNOWN, never 'no retime' — and cross-check with the Sm2TimeMap slope in a saved Project.db or the EXPORT_EDL M2 rate. Do NOT read speed with GetProperty (None), witness it via EXPORT_FCP_7_XML (degenerate), or judge it from GetLeftOffset/GetRightOffset — the 2.80.0 revision of this entry recommended that pair as the witness and it is blind by construction: it reads the warped domain (position / speed) and its span equals the record span at every speed. Keep it for PLACEMENT checks only. Reverse and varying-speed maps remain untested as import routes. And if you are importing a real Premiere XML that contains retimes, treat every retimed clip's source position as WRONG — placed at <in>, i.e. in / ratio — until it is checked against a reference; the lengths and the links will look right.
|
|
122
122
|
- **Tags:** missing-method, timeline, retime, speed, interchange, silent-failure, unreliable-return
|
|
123
123
|
|
|
124
124
|
### Color node graph editing and primary grade values
|
|
@@ -363,7 +363,7 @@ values, or automation-hostile modal prompts.
|
|
|
363
363
|
- **Object:** `TimelineItem`
|
|
364
364
|
- **Signature:** `() -> int`
|
|
365
365
|
- **Behavior:** Reads back one frame off on some items. Measured while verifying a constructed timeline against the clipInfos it was built from: for 4/4 items GetLeftOffset returned exactly the startFrame that was sent, while GetSourceStartFrame disagreed by 1 on some of the same items. The two are supposed to describe the same edit point, so a conform that verifies placement with GetSourceStartFrame reports phantom off-by-one drift on correctly placed clips — and would hide a real one-frame error just as easily.
|
|
366
|
-
- **Workaround / current handling:** Verify source-side placement with GetLeftOffset, which is exact. Treat GetSourceStartFrame as approximate, and never diff it against a sent startFrame to decide whether a clip landed right.
|
|
366
|
+
- **Workaround / current handling:** Verify source-side placement with GetLeftOffset, which is exact. Treat GetSourceStartFrame as approximate, and never diff it against a sent startFrame to decide whether a clip landed right. Scope: placement at 100% speed. On a retimed clip the two read DIFFERENT domains — GetLeftOffset is warped (position / speed), GetSourceStartFrame is true source — see the retime entry's witness calibration before comparing them.
|
|
367
367
|
- **Tags:** off-by-one, unreliable-return, timeline, conform, verify
|
|
368
368
|
|
|
369
369
|
### hasattr() / getattr() on Resolve API objects (attribute fabrication)
|
package/install.py
CHANGED
|
@@ -36,7 +36,7 @@ from src.utils.update_check import (
|
|
|
36
36
|
|
|
37
37
|
# ─── Version ──────────────────────────────────────────────────────────────────
|
|
38
38
|
|
|
39
|
-
VERSION = "2.80.
|
|
39
|
+
VERSION = "2.80.2"
|
|
40
40
|
# Only hard floor: mcp[cli] requires Python 3.10+. There is no upper bound —
|
|
41
41
|
# Resolve's scripting bridge loads into newer interpreters on recent builds
|
|
42
42
|
# (Python 3.14 verified against Resolve Studio 20.3.2). Older Resolve builds
|
package/package.json
CHANGED
|
@@ -24,7 +24,7 @@ cannot drift.
|
|
|
24
24
|
| Platform | File(s) | Trigger | Source |
|
|
25
25
|
|---|---|---|---|
|
|
26
26
|
| Codex / OpenCode / Zed / others | `AGENTS.md` (`## Domain Routing` block) | always-on | generated block |
|
|
27
|
-
| Claude Code | `.claude/skills
|
|
27
|
+
| Claude Code | `.claude/skills/<name>/SKILL.md` | semantic (description) | hand-authored (rich) |
|
|
28
28
|
| Cursor | `.cursor/rules/*.mdc` | `alwaysApply` repo rule + per-domain `description` (agent-requested) | generated |
|
|
29
29
|
| VS Code / Copilot | `.github/copilot-instructions.md` + `.github/instructions/*.instructions.md` | always-on + `applyTo` | generated |
|
|
30
30
|
| Windsurf | `.windsurf/rules/*.md` + `.windsurfrules` | rules dir + legacy flat | generated |
|
|
@@ -33,7 +33,10 @@ cannot drift.
|
|
|
33
33
|
| Continue | `.continue/rules/resolve-mcp.md` | always-on | generated |
|
|
34
34
|
| Claude Desktop | — (chat client, no repo rules) | — | MCP prompts only |
|
|
35
35
|
|
|
36
|
-
`.claude/skills/*` stays hand-authored (Claude's rich semantic skills)
|
|
36
|
+
`.claude/skills/*` stays hand-authored (Claude's rich semantic skills). Each
|
|
37
|
+
one is a **directory** holding a `SKILL.md` named for its frontmatter `name`;
|
|
38
|
+
a loose `.md` at the top level of `.claude/skills/` is never discovered and
|
|
39
|
+
fails silently. Every
|
|
37
40
|
other file is generated. `AGENTS.md` remains the universal backstop for any
|
|
38
41
|
client not listed. Always-on rule dirs (Cline/Roo/Continue) get one compact
|
|
39
42
|
combined file (repo hygiene + domain table), since they load every rule file
|
|
@@ -61,8 +64,8 @@ node scripts/agent-rules/generate.mjs --check # exit 1 if anything is stale (C
|
|
|
61
64
|
## Adding a domain or a platform
|
|
62
65
|
|
|
63
66
|
- **New domain:** add an entry to `DOMAINS` in `generate.mjs`, add a matching
|
|
64
|
-
`@mcp.prompt` in `src/server.py`, and (optionally) a rich
|
|
65
|
-
Regenerate.
|
|
67
|
+
`@mcp.prompt` in `src/server.py`, and (optionally) a rich
|
|
68
|
+
`.claude/skills/<name>/SKILL.md`. Regenerate.
|
|
66
69
|
- **New platform:** add an `emit(...)` for its convention in `generate.mjs`,
|
|
67
70
|
reusing `domainBody(d)` / `repoHygiene`. Regenerate. Do not hand-edit generated
|
|
68
71
|
files — the drift guard will fail.
|
package/src/granular/common.py
CHANGED
|
@@ -85,7 +85,7 @@ if not logging.getLogger().handlers:
|
|
|
85
85
|
handlers=[logging.StreamHandler()],
|
|
86
86
|
)
|
|
87
87
|
|
|
88
|
-
VERSION = "2.80.
|
|
88
|
+
VERSION = "2.80.2"
|
|
89
89
|
logger = logging.getLogger("davinci-resolve-mcp")
|
|
90
90
|
logger.info(f"Starting DaVinci Resolve MCP Server v{VERSION}")
|
|
91
91
|
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 341-tool granular server instead
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
|
-
VERSION = "2.80.
|
|
14
|
+
VERSION = "2.80.2"
|
|
15
15
|
|
|
16
16
|
import base64
|
|
17
17
|
import os
|
|
@@ -1962,9 +1962,67 @@ def _marker_display_frame(tl, frame):
|
|
|
1962
1962
|
return frame
|
|
1963
1963
|
|
|
1964
1964
|
|
|
1965
|
-
def
|
|
1965
|
+
def _playhead_absolute_timecode(tl, timecode):
|
|
1966
|
+
"""Lift an elapsed timecode to the absolute timecode SetCurrentTimecode wants.
|
|
1967
|
+
|
|
1968
|
+
Timeline.SetCurrentTimecode only accepts the absolute timeline timecode
|
|
1969
|
+
shown in the Resolve UI; handing it a timecode below the start timecode
|
|
1970
|
+
returns False with no error info (measured on Studio 19.1.3.7: on a
|
|
1971
|
+
timeline starting 00:59:50:00, '00:00:21:03' fails while '01:00:11:03'
|
|
1972
|
+
succeeds). Mirror the marker-param contract: a timecode that parses to a
|
|
1973
|
+
frame below the timeline start is elapsed time and gets lifted by the
|
|
1974
|
+
start frame. At-or-past-start timecodes, and strings this parser cannot
|
|
1975
|
+
read, pass through unchanged so Resolve stays the arbiter of them.
|
|
1976
|
+
"""
|
|
1977
|
+
if not isinstance(timecode, str):
|
|
1978
|
+
return timecode
|
|
1979
|
+
frame, err = _timeline_timecode_to_frame_id(tl, timecode)
|
|
1980
|
+
if err:
|
|
1981
|
+
return timecode
|
|
1982
|
+
start = _timeline_start_frame(tl)
|
|
1983
|
+
if not start or frame >= start:
|
|
1984
|
+
return timecode
|
|
1985
|
+
fps, fps_err = _timeline_fps(tl)
|
|
1986
|
+
if fps_err:
|
|
1987
|
+
return timecode
|
|
1988
|
+
drop_frame = ";" in timecode
|
|
1989
|
+
try:
|
|
1990
|
+
start_tc = tl.GetStartTimecode()
|
|
1991
|
+
except Exception:
|
|
1992
|
+
start_tc = None
|
|
1993
|
+
if isinstance(start_tc, str) and start_tc:
|
|
1994
|
+
drop_frame = ";" in start_tc
|
|
1995
|
+
separator = ";" if drop_frame else ":"
|
|
1996
|
+
return _frame_id_to_timecode(
|
|
1997
|
+
frame + start, fps, separator=separator, drop_frame=drop_frame
|
|
1998
|
+
)
|
|
1999
|
+
|
|
2000
|
+
|
|
2001
|
+
def _frame_id_to_timecode(
|
|
2002
|
+
frame: int, fps: float, separator: str = ":", drop_frame: bool = False
|
|
2003
|
+
) -> str:
|
|
1966
2004
|
nominal_fps = max(1, int(round(float(fps))))
|
|
1967
2005
|
frame = max(0, int(frame))
|
|
2006
|
+
if drop_frame:
|
|
2007
|
+
# Inverse of the drop-frame arithmetic in _timecode_to_frame_id: 2 (30
|
|
2008
|
+
# fps) or 4 (60 fps) frame numbers are skipped each minute except every
|
|
2009
|
+
# tenth minute.
|
|
2010
|
+
drop = int(round(nominal_fps * 0.0666666667))
|
|
2011
|
+
if drop > 0:
|
|
2012
|
+
per_minute = nominal_fps * 60 - drop
|
|
2013
|
+
per_ten = per_minute * 10 + drop
|
|
2014
|
+
tens, rem = divmod(frame, per_ten)
|
|
2015
|
+
if rem < nominal_fps * 60:
|
|
2016
|
+
minutes = tens * 10
|
|
2017
|
+
frame_in_minute = rem
|
|
2018
|
+
else:
|
|
2019
|
+
rem -= nominal_fps * 60
|
|
2020
|
+
extra_minutes, frame_in_minute = divmod(rem, per_minute)
|
|
2021
|
+
minutes = tens * 10 + 1 + extra_minutes
|
|
2022
|
+
frame_in_minute += drop
|
|
2023
|
+
hours, minutes = divmod(minutes, 60)
|
|
2024
|
+
seconds, frames = divmod(frame_in_minute, nominal_fps)
|
|
2025
|
+
return f"{hours:02d}:{minutes:02d}:{seconds:02d}{separator}{frames:02d}"
|
|
1968
2026
|
total_seconds, frames = divmod(frame, nominal_fps)
|
|
1969
2027
|
hours, rem = divmod(total_seconds, 3600)
|
|
1970
2028
|
minutes, seconds = divmod(rem, 60)
|
|
@@ -21623,9 +21681,12 @@ def timeline_markers(action: str, params: Optional[Dict[str, Any]] = None) -> An
|
|
|
21623
21681
|
|
|
21624
21682
|
Marker frames are RELATIVE to the timeline start: frame 0 is the first
|
|
21625
21683
|
frame of the timeline, even when the timeline starts at 01:00:00:00.
|
|
21626
|
-
|
|
21627
|
-
(timecodes before the start timecode are treated as elapsed
|
|
21628
|
-
converted to relative frames automatically.
|
|
21684
|
+
Marker timecode params are absolute timeline timecode as shown in the
|
|
21685
|
+
Resolve UI (timecodes before the start timecode are treated as elapsed
|
|
21686
|
+
time) and are converted to relative frames automatically.
|
|
21687
|
+
set_current_timecode accepts the same convention: elapsed timecodes below
|
|
21688
|
+
the start timecode are lifted to absolute before calling Resolve, which
|
|
21689
|
+
itself refuses sub-start timecodes with a bare False.
|
|
21629
21690
|
|
|
21630
21691
|
Actions:
|
|
21631
21692
|
add(frame|frame_id|frameId|timecode?, color?, name?, note?, duration?, custom_data?) -> {success, frame}
|
|
@@ -21690,7 +21751,7 @@ def timeline_markers(action: str, params: Optional[Dict[str, Any]] = None) -> An
|
|
|
21690
21751
|
elif action == "get_current_timecode":
|
|
21691
21752
|
return {"timecode": tl.GetCurrentTimecode()}
|
|
21692
21753
|
elif action == "set_current_timecode":
|
|
21693
|
-
return {"success": bool(tl.SetCurrentTimecode(p["timecode"]))}
|
|
21754
|
+
return {"success": bool(tl.SetCurrentTimecode(_playhead_absolute_timecode(tl, p["timecode"])))}
|
|
21694
21755
|
elif action == "get_current_video_item":
|
|
21695
21756
|
it = tl.GetCurrentVideoItem()
|
|
21696
21757
|
return {"name": it.GetName(), "id": it.GetUniqueId()} if it else {"name": None, "id": None}
|
package/src/utils/api_truth.py
CHANGED
|
@@ -645,7 +645,11 @@ API_TRUTH: List[Dict[str, Any]] = [
|
|
|
645
645
|
"a real one-frame error just as easily.",
|
|
646
646
|
"recommended": "Verify source-side placement with GetLeftOffset, which is exact. "
|
|
647
647
|
"Treat GetSourceStartFrame as approximate, and never diff it "
|
|
648
|
-
"against a sent startFrame to decide whether a clip landed right."
|
|
648
|
+
"against a sent startFrame to decide whether a clip landed right. "
|
|
649
|
+
"Scope: placement at 100% speed. On a retimed clip the two read "
|
|
650
|
+
"DIFFERENT domains — GetLeftOffset is warped (position / speed), "
|
|
651
|
+
"GetSourceStartFrame is true source — see the retime entry's "
|
|
652
|
+
"witness calibration before comparing them.",
|
|
649
653
|
"tags": ["off-by-one", "unreliable-return", "timeline", "conform", "verify"],
|
|
650
654
|
"submit": "bug",
|
|
651
655
|
},
|
|
@@ -710,10 +714,14 @@ API_TRUTH: List[Dict[str, Any]] = [
|
|
|
710
714
|
"one 200% clip per timeline, a document carrying warped "
|
|
711
715
|
"<in>/<out>, true-source pproTicks, <duration> = fileLen/ratio "
|
|
712
716
|
"and a constant-slope graphdict imports cleanly, the control "
|
|
713
|
-
"lands correct, and
|
|
714
|
-
"
|
|
715
|
-
"
|
|
716
|
-
"
|
|
717
|
+
"lands correct, and NO retime is built: recalibrated 2026-08-05, "
|
|
718
|
+
"every xmeml-imported clip carries a DEGENERATE time map in "
|
|
719
|
+
"Project.db (Sm2TimeMap with an empty source axis — five Time "
|
|
720
|
+
"Remap shapes re-measured, 15/15 clips degenerate), so no speed "
|
|
721
|
+
"exists in the project data, and the API source witness reads "
|
|
722
|
+
"0/0 on those clips (see WITNESS CALIBRATION below); emitting "
|
|
723
|
+
"the identical document WITHOUT the graphdict gives the "
|
|
724
|
+
"identical result; (c) `reverse` does not survive "
|
|
717
725
|
"either; (d) THE HAZARD, and it is the part that bites: Resolve "
|
|
718
726
|
"reads <in> LITERALLY as the true source frame, honouring neither "
|
|
719
727
|
"the ticks nor the graphdict. Import a genuine Premiere XML that "
|
|
@@ -750,24 +758,76 @@ API_TRUTH: List[Dict[str, Any]] = [
|
|
|
750
758
|
"verifying a retime by round-tripping through EXPORT_FCP_7_XML "
|
|
751
759
|
"is reading furniture, and the identity Time Remap blocks "
|
|
752
760
|
"present on every clip are what make the route look like it "
|
|
753
|
-
"should work."
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
761
|
+
"should work. "
|
|
762
|
+
"WITNESS CALIBRATION (2026-08-05, Studio 19.1.3.7) — the "
|
|
763
|
+
"positive control this entry previously lacked now exists, and "
|
|
764
|
+
"it RETRACTS the witness the 2.80.0 revision of this entry "
|
|
765
|
+
"recommended. The rig removed every confound: the SAME clip "
|
|
766
|
+
"placed twice, adjacent, in ONE timeline, the second copy "
|
|
767
|
+
"hand-set to 200% in the UI (the only way to make one — see "
|
|
768
|
+
"above). GetSourceStartFrame/GetSourceEndFrame separated the "
|
|
769
|
+
"copies — 1822..1870 (span 48) at 100% vs 1822..1918 (span 96) "
|
|
770
|
+
"at 200% — while GetLeftOffset/GetRightOffset did NOT: "
|
|
771
|
+
"1822..1870 at 100% vs 911..959 at 200%, which is exactly "
|
|
772
|
+
"position / 2. GetLeftOffset reports the WARPED (record-side) "
|
|
773
|
+
"domain — position / speed, the `In` column of Project.db's "
|
|
774
|
+
"Sm2TiItem — so it is exact for PLACEMENT and blind for SPEED "
|
|
775
|
+
"BY CONSTRUCTION: its span equals the record span at every "
|
|
776
|
+
"speed. The speed itself lives in the item's Sm2TimeMap blob "
|
|
777
|
+
"(keyframe slope = ratio; the hand-set 200% reads slope exactly "
|
|
778
|
+
"2.0), which is what GetSourceStart/EndFrame and EXPORT_EDL "
|
|
779
|
+
"read. THE SPEED WITNESS is therefore the GetSourceStart/"
|
|
780
|
+
"EndFrame span vs the record duration. CAVEAT: on "
|
|
781
|
+
"xmeml-IMPORTED timelines those return 0/0 — the importer "
|
|
782
|
+
"leaves the time map's source axis empty — and a 0/0 read is "
|
|
783
|
+
"UNKNOWN, never 'no retime'. Cross-checks that work everywhere: "
|
|
784
|
+
"the Sm2TimeMap slope read from a saved Project.db, and the "
|
|
785
|
+
"EXPORT_EDL M2 rate (rate = fps x speed/100, so 048.0 = 200% at "
|
|
786
|
+
"24 fps; `M2 ... 000.0` on every clip is the degenerate-map "
|
|
787
|
+
"furniture of an xmeml import — ignore it). "
|
|
788
|
+
"TWO IMPORT ROUTES DO BUILD CONSTANT RETIMES (measured "
|
|
789
|
+
"2026-08-05, media linked, judged via the calibrated witnesses "
|
|
790
|
+
"above): (1) OTIO LinearTimeWarp through ImportTimelineFromFile "
|
|
791
|
+
"— 200% (src 200..296 over a 48-frame record) and 50% (src "
|
|
792
|
+
"300..324 over a 48-frame record) both landed with correct "
|
|
793
|
+
"source in-points; the saved Project.db shows slope 2.0 and "
|
|
794
|
+
"0.5. Emission rules: the document must be Resolve-shaped with "
|
|
795
|
+
"TIMECODE-ABSOLUTE source frames (see the ImportTimelineFromFile "
|
|
796
|
+
".otio entry), the effect is `LinearTimeWarp.1` with "
|
|
797
|
+
"`time_scalar`, and `source_range.duration` is the RECORD span "
|
|
798
|
+
"— OTIO semantics, the time_scalar handles source consumption; "
|
|
799
|
+
"sending the source span as the duration builds a "
|
|
800
|
+
"spec-correctly longer clip, not a retime. (2) EDL M2 — 200% "
|
|
801
|
+
"landed (src 100..196 over a 48-frame record), linked. Author "
|
|
802
|
+
"the shape Resolve's own EXPORT_EDL writes: the event line's "
|
|
803
|
+
"source span EQUALS the record span even under M2; the "
|
|
804
|
+
"`M2 <reel> <rate> <srcInTC>` line carries the play rate in "
|
|
805
|
+
"fps (048.0 = 200% at 24); `* FROM CLIP NAME:` comments drive "
|
|
806
|
+
"pool linking. UNTESTED as import routes: reverse (negative "
|
|
807
|
+
"time_scalar / reverse M2) and varying-speed maps — do not "
|
|
808
|
+
"extrapolate the constant-speed result to them.",
|
|
809
|
+
"recommended": "Set clip speed/retime in the Resolve UI, or BUILD it by "
|
|
810
|
+
"import: OTIO LinearTimeWarp and EDL M2 both construct "
|
|
811
|
+
"constant retimes through ImportTimelineFromFile (measured "
|
|
812
|
+
"— emission rules in reality above); xmeml does not, in any "
|
|
813
|
+
"Time Remap shape. To READ a retime back, judge speed by "
|
|
814
|
+
"the GetSourceStart/EndFrame span vs the record duration — "
|
|
815
|
+
"a 0/0 read (xmeml-imported timelines) is UNKNOWN, never "
|
|
816
|
+
"'no retime' — and cross-check with the Sm2TimeMap slope in "
|
|
817
|
+
"a saved Project.db or the EXPORT_EDL M2 rate. Do NOT read "
|
|
818
|
+
"speed with GetProperty (None), witness it via "
|
|
819
|
+
"EXPORT_FCP_7_XML (degenerate), or judge it from "
|
|
820
|
+
"GetLeftOffset/GetRightOffset — the 2.80.0 revision of this "
|
|
821
|
+
"entry recommended that pair as the witness and it is blind "
|
|
822
|
+
"by construction: it reads the warped domain (position / "
|
|
823
|
+
"speed) and its span equals the record span at every speed. "
|
|
824
|
+
"Keep it for PLACEMENT checks only. Reverse and "
|
|
825
|
+
"varying-speed maps remain untested as import routes. And "
|
|
826
|
+
"if you are importing a real Premiere XML that contains "
|
|
827
|
+
"retimes, treat every retimed clip's source position as "
|
|
828
|
+
"WRONG — placed at <in>, i.e. in / ratio — until it is "
|
|
829
|
+
"checked against a reference; the lengths and the links "
|
|
830
|
+
"will look right.",
|
|
771
831
|
"tags": ["missing-method", "timeline", "retime", "speed", "interchange",
|
|
772
832
|
"silent-failure", "unreliable-return"],
|
|
773
833
|
"submit": "missing",
|