davinci-resolve-mcp 2.220.0 → 2.222.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/CHANGELOG.md CHANGED
@@ -2,6 +2,94 @@
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.222.0 — native Resolve 21.1 timecode and waveform alignment
6
+
7
+ Contributed by @legionsound (#212), live-validated on Studio 21.1.0.14.
8
+
9
+ ### Added
10
+
11
+ - **`timeline auto_align_clips`**, with the granular twin
12
+ `auto_align_timeline_clips`, calling native 21.1 `AutoAlignClips` on explicit
13
+ timeline item IDs, with the documented `SyncUsing` and `UseTrack` options
14
+ accepting either constant names or integral native values. Every ID resolves
15
+ before anything moves, order is preserved, malformed input is refused, and a
16
+ native `false` stays `false`. Tool count 374 → 375.
17
+ - **The wrapper does not silently expand the selection.** On the measured
18
+ build, waveform alignment refused a video-only selection, and an audio-only
19
+ selection aligned the audio while leaving its linked video at the old
20
+ position. Rather than quietly adding the linked items — which would move
21
+ clips the caller never named — the tool documentation tells callers to
22
+ include both sides of a linked pair, and an incomplete selection gets an
23
+ honest refusal it can act on.
24
+
25
+ ### Validation
26
+
27
+ - Full suite green: 3,465 passed, 1 skipped. `timeline.auto_align_clips`
28
+ probed directly: MEDIUM / destructive / recognised, and present in the
29
+ destructive registry.
30
+ - Live evidence is @legionsound's on Studio 21.1.0.14, and the two modes carry
31
+ **different strengths of evidence**, kept distinct rather than averaged:
32
+ timecode alignment is a position result (sources one second apart, starts
33
+ 0/48 moving to 0/24), while waveform/MIX was verified against rendered
34
+ output — identical speech starts 0/24 moving to 0/0, with complete decoded
35
+ video **and PCM audio** matching independently positioned manual reference
36
+ timelines exactly, for both wrappers. Not reproduced here; this machine is
37
+ Studio 19.1.3.7, below the 21.1 floor.
38
+ - **Not claimed**: other microphones, drift, variable frame rates, other track
39
+ selections and long recordings. Smart Switch remains separate and unstarted.
40
+
41
+ ### Changed
42
+
43
+ - Adapted on merge, as with #213 and #214: counts resolved to **375**,
44
+ confirmed by the agent-rule generator, generated files regenerated rather
45
+ than hand-merged. No behaviour changed in the adaptation.
46
+
47
+ ## What's New in v2.221.0 — native Resolve 21.1 audio level normalization
48
+
49
+ Contributed by @legionsound (#214), live-validated on Studio 21.1.0.14.
50
+
51
+ ### Added
52
+
53
+ - **`timeline normalize_audio_level`**, with the granular twin
54
+ `normalize_timeline_audio_level`, calling native 21.1 `NormalizeAudioLevel`
55
+ on explicit audio timeline item IDs. All four `NormalizeAudioOptions` fields
56
+ are supported — `normalizationMode`, `targetLevel` in dBFS, `targetLoudness`
57
+ in LKFS and `setLevelMode` — with either documented constant names or
58
+ integral native values. Every ID resolves before anything is written;
59
+ duplicate or missing IDs and malformed options are refused with nothing
60
+ changed. A native `false` stays `false`, and native defaults stay native
61
+ defaults rather than being pre-filled by the wrapper.
62
+ - Registered in **both** write tables with a 21.1 callable-method floor,
63
+ destructive granular annotations and compound dry-run refusal tests. Tool
64
+ count 373 → 374.
65
+ - **Source audio files are untouched** — this writes clip levels on the
66
+ timeline, not the media on disk.
67
+
68
+ ### Validation
69
+
70
+ - Full suite green: 3,461 passed, 1 skipped. `timeline.normalize_audio_level`
71
+ probed directly: MEDIUM / destructive / recognised in the classifier, and
72
+ present in the destructive registry.
73
+ - Live evidence is @legionsound's on Studio 21.1.0.14, and it is measured from
74
+ the **exported audio** rather than from a readback — independent FFmpeg
75
+ analysis of WAVs the wrappers actually produced: relative peak normalization
76
+ to −6 dBFS preserved the source 12 dB difference (−6.0 / −18.0 dBFS),
77
+ independent peak normalization measured −6.0 / −6.0 dBFS, and an EBU R128
78
+ target of −23 LKFS measured −22.9 LUFS, within 0.1 LU. Complete decoded PCM
79
+ was identical between both interfaces in every case. Not reproduced here;
80
+ this machine is Studio 19.1.3.7, below the 21.1 floor.
81
+ - **Not claimed**: other normalization modes, difficult true-peak limiting,
82
+ multichannel routing, long programs and other source formats.
83
+
84
+ ### Changed
85
+
86
+ - Adapted on merge, the same way #213 was. The branch was rebased onto
87
+ v2.219.0, so its 370 → 371 count bump and every generated agent-rule file
88
+ collided with the 373 that output blanking had landed. Counts resolved to
89
+ **374**, confirmed independently by the agent-rule generator, and the
90
+ generated files regenerated rather than hand-merged. No behaviour changed in
91
+ the adaptation.
92
+
5
93
  ## What's New in v2.220.0 — native Resolve 21.1 output blanking, timeline and clip
6
94
 
7
95
  Contributed by @legionsound (#213), live-validated on Studio 21.1.0.14.
package/README.md CHANGED
@@ -2,10 +2,10 @@
2
2
 
3
3
  English | [简体中文](README.zh-CN.md)
4
4
 
5
- [![Version](https://img.shields.io/badge/version-2.220.0-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-2.222.0-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
6
6
  [![npm](https://img.shields.io/npm/v/davinci-resolve-mcp.svg?label=npm&color=CB3837)](https://www.npmjs.com/package/davinci-resolve-mcp)
7
7
  [![API Coverage](https://img.shields.io/badge/API%20Coverage-100%25-brightgreen.svg)](docs/reference/api-coverage.md)
8
- [![Tools](https://img.shields.io/badge/MCP%20Tools-36%20(373%20full)-blue.svg)](#server-modes)
8
+ [![Tools](https://img.shields.io/badge/MCP%20Tools-36%20(375%20full)-blue.svg)](#server-modes)
9
9
  [![Advanced](https://img.shields.io/badge/Advanced%20(offline)-18%20tools-blueviolet.svg)](#server-modes)
10
10
  [![Tested](https://img.shields.io/badge/Live%20Tested-93.6%25-green.svg)](docs/reference/api-coverage.md#test-results)
11
11
  [![DaVinci Resolve](https://img.shields.io/badge/DaVinci%20Resolve-18.5+-darkred.svg)](https://www.blackmagicdesign.com/products/davinciresolve)
@@ -133,7 +133,7 @@ The command starts a loopback-only server and opens the control panel in your br
133
133
  | Mode | Entry point | Tools | Best for |
134
134
  |------|-------------|-------|----------|
135
135
  | Compound | `src/server.py` | 36 | Default mode for most assistants. Related Resolve operations are grouped behind action parameters to keep context usage low. |
136
- | Full / granular | `src/server.py --full` or `src/resolve_mcp_server.py` | 373 | Power users who want one MCP tool per Resolve API method. |
136
+ | Full / granular | `src/server.py --full` or `src/resolve_mcp_server.py` | 375 | Power users who want one MCP tool per Resolve API method. |
137
137
 
138
138
  The compound server is recommended unless you specifically need the granular one-tool-per-method surface.
139
139
 
@@ -365,7 +365,7 @@ The default server is a local stdio process launched by your MCP client; it does
365
365
 
366
366
  | Metric | Value |
367
367
  |--------|-------|
368
- | MCP Tools | **36** compound / **373** granular (live server) |
368
+ | MCP Tools | **36** compound / **375** granular (live server) |
369
369
  | Advanced (offline) tools | **18** — .drp/.drt/.drx + DB authoring, no Resolve running |
370
370
  | Kernel Actions | **136** guarded workflow actions across 9 compound tools |
371
371
  | API Methods Covered | **361/361** (100%) |
package/README.zh-CN.md CHANGED
@@ -2,17 +2,17 @@
2
2
 
3
3
  [English](README.md) | 简体中文
4
4
 
5
- [![Version](https://img.shields.io/badge/version-2.220.0-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-2.222.0-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
6
6
  [![npm](https://img.shields.io/npm/v/davinci-resolve-mcp.svg?label=npm&color=CB3837)](https://www.npmjs.com/package/davinci-resolve-mcp)
7
7
  [![API Coverage](https://img.shields.io/badge/API%20Coverage-100%25-brightgreen.svg)](docs/reference/api-coverage.md)
8
- [![Tools](https://img.shields.io/badge/MCP%20Tools-36%20(373%20full)-blue.svg)](#服务器模式)
8
+ [![Tools](https://img.shields.io/badge/MCP%20Tools-36%20(375%20full)-blue.svg)](#服务器模式)
9
9
  [![Advanced](https://img.shields.io/badge/Advanced%20(offline)-18%20tools-blueviolet.svg)](#服务器模式)
10
10
  [![Tested](https://img.shields.io/badge/Live%20Tested-93.6%25-green.svg)](docs/reference/api-coverage.md#test-results)
11
11
  [![DaVinci Resolve](https://img.shields.io/badge/DaVinci%20Resolve-18.5+-darkred.svg)](https://www.blackmagicdesign.com/products/davinciresolve)
12
12
  [![Python](https://img.shields.io/badge/python-3.10+-green.svg)](https://www.python.org/downloads/)
13
13
  [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT)
14
14
 
15
- > 本翻译对应 v2.220.0 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
15
+ > 本翻译对应 v2.222.0 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
16
16
 
17
17
  一个 Model Context Protocol (MCP) 服务器,让 AI 助手通过官方脚本 API 控制 DaVinci Resolve Studio(达芬奇)。它提供完整的 API 覆盖,外加带护栏的工作流助手,涵盖剪辑、媒体池整理、渲染设置、审阅标记、调色、Fusion、Fairlight、项目生命周期任务、扩展开发,以及不碰源媒体的媒体分析。
18
18
 
@@ -88,7 +88,7 @@ venv/bin/python -m src.control_panel
88
88
  | 模式 | 入口 | 工具数 | 适合谁 |
89
89
  |------|------|--------|--------|
90
90
  | Compound(复合) | `src/server.py` | 36 | 大多数助手的默认模式。相关的 Resolve 操作按 action 参数分组,压低上下文占用。 |
91
- | Full / granular(细粒度) | `src/server.py --full` 或 `src/resolve_mcp_server.py` | 373 | 想要"一个 Resolve API 方法 = 一个 MCP 工具"的重度用户。 |
91
+ | Full / granular(细粒度) | `src/server.py --full` 或 `src/resolve_mcp_server.py` | 375 | 想要"一个 Resolve API 方法 = 一个 MCP 工具"的重度用户。 |
92
92
 
93
93
  除非你明确需要一方法一工具的细粒度界面,否则推荐复合模式。
94
94
 
@@ -226,7 +226,7 @@ DRX 调色写入**针对 Resolve Studio 做过实机校准**:调色参数默
226
226
 
227
227
  | 指标 | 数值 |
228
228
  |------|------|
229
- | MCP 工具 | **36** 复合 / **373** 细粒度(实时服务器) |
229
+ | MCP 工具 | **36** 复合 / **375** 细粒度(实时服务器) |
230
230
  | Advanced(离线)工具 | **18**——.drp/.drt/.drx + 数据库创作,无需 Resolve 运行 |
231
231
  | 内核 action | 9 个复合工具下 **136** 个带护栏的工作流 action |
232
232
  | API 方法覆盖 | **361/361**(100%) |
package/docs/SKILL.md CHANGED
@@ -345,7 +345,7 @@ to the user as verified.
345
345
  | Mode | Entry point | Tool count | Use when |
346
346
  |---|---|---|---|
347
347
  | Compound (default) | `src/server.py` | 36 tools | Most workflows — keeps context lean |
348
- | Granular (full) | `src/server.py --full` | 373 tools | Power users needing one tool per API method |
348
+ | Granular (full) | `src/server.py --full` | 375 tools | Power users needing one tool per API method |
349
349
 
350
350
  Resolve 21.1 adds [twelve read-only discovery controls](reference/resolve211-read-controls.md)
351
351
  for edition, presets, audio formats/codecs, normalization modes, speed, fades
@@ -2486,3 +2486,7 @@ Native 21.1 transition creation: see [transition controls](reference/resolve211-
2486
2486
 
2487
2487
  Native multicam creation and flattening: [21.1 controls](reference/resolve211-multicam.md), with contributor render evidence and remaining family coverage.
2488
2488
  Native timeline/clip output blanking: [21.1 controls](reference/resolve211-blanking.md), with explicit inheritance and pixel-bound evidence.
2489
+
2490
+ Native audio normalization: [21.1 controls](reference/resolve211-normalization.md), with independently measured exported-audio evidence.
2491
+
2492
+ Native timecode/waveform alignment: [21.1 controls](reference/resolve211-alignment.md), including linked-item selection semantics and rendered video/audio evidence.
@@ -64,7 +64,7 @@ davinci-resolve-mcp/
64
64
  ├── install.py # Universal installer (macOS/Windows/Linux)
65
65
  ├── src/
66
66
  │ ├── server.py # Compound MCP server — 36 tools (default)
67
- │ ├── resolve_mcp_server.py # Thin full-server entrypoint — 373 tools
67
+ │ ├── resolve_mcp_server.py # Thin full-server entrypoint — 375 tools
68
68
  │ ├── granular/ # Modular full-server implementation
69
69
  │ └── utils/ # Platform detection, Resolve connection helpers
70
70
  ├── tests/ # offline suite (test_*.py) + live harnesses (live_*.py):
package/docs/install.md CHANGED
@@ -144,7 +144,7 @@ The MCP server comes in two modes:
144
144
  | Mode | File | Tools | Best For |
145
145
  |------|------|-------|----------|
146
146
  | **Compound** (default) | `src/server.py` | 36 | Most users — fast, clean, low context usage |
147
- | **Full** | `src/resolve_mcp_server.py` | 373 | Power users who want one tool per API method |
147
+ | **Full** | `src/resolve_mcp_server.py` | 375 | Power users who want one tool per API method |
148
148
 
149
149
  The compound server's `timeline_item` tool includes dedicated actions for common workflows:
150
150
 
@@ -159,7 +159,7 @@ The compound server's `timeline_item` tool includes dedicated actions for common
159
159
 
160
160
  The installer uses the compound server by default. To use the full server:
161
161
  ```bash
162
- python src/server.py --full # Launch full 373-tool server
162
+ python src/server.py --full # Launch full 375-tool server
163
163
  # Or point your MCP config directly at src/resolve_mcp_server.py
164
164
  ```
165
165
 
@@ -25,7 +25,7 @@ Every non-deprecated method in the bundled legacy README is represented. This
25
25
  does not claim complete coverage of the newer Resolve 21.1 typed API. The
26
26
  default compound server exposes **36 tools** that group related operations by
27
27
  action parameter, keeping LLM context windows lean. The full granular server
28
- provides **373 individual tools** for power users. The legacy coverage spans
28
+ provides **375 individual tools** for power users. The legacy coverage spans
29
29
  13 API object classes. MCP-level kernel actions are tracked separately in
30
30
  [Kernel Action Coverage](../kernels/README.md).
31
31
 
@@ -636,3 +636,7 @@ Native 21.1 transition creation: see [transition controls](resolve211-native-tra
636
636
 
637
637
  Native multicam creation and flattening: [21.1 controls](resolve211-multicam.md), with contributor render evidence and remaining family coverage.
638
638
  Native timeline/clip output blanking: [21.1 controls](resolve211-blanking.md), with explicit inheritance and pixel-bound evidence.
639
+
640
+ Native audio normalization: [21.1 controls](resolve211-normalization.md), with independently measured exported-audio evidence.
641
+
642
+ Native timecode/waveform alignment: [21.1 controls](resolve211-alignment.md), including linked-item selection semantics and rendered video/audio evidence.
@@ -0,0 +1,54 @@
1
+ # Native Resolve 21.1 timeline alignment
2
+
3
+ Compound `timeline auto_align_clips` and granular `auto_align_timeline_clips`
4
+ accept `item_ids` (timeline item unique IDs, not media-pool IDs) and optional
5
+ `options`. All IDs must resolve in the current timeline's video/audio tracks;
6
+ missing or duplicate IDs are refused before writes. The requested order and
7
+ selection are preserved. This command does not implicitly add linked items.
8
+
9
+ **Include both video and linked audio IDs when synchronizing camera clips.**
10
+ Contributor observation on 21.1.0.14: waveform alignment of the video items alone
11
+ returned false; audio-only selection moved the audio while linked video stayed
12
+ put. Explicitly selecting all video/audio items aligned the complete fixture.
13
+ Do not assume that selecting one half of a linked pair moves the other half.
14
+
15
+ Options follow AutoAlignOptions: SyncUsing accepts
16
+ AUTO_ALIGN_CLIPS_USING_TIMECODE or AUTO_ALIGN_CLIPS_USING_WAVEFORM; UseTrack
17
+ accepts a native track number or AUTO_ALIGN_CLIPS_WAVEFORM_TRACK_MIX /
18
+ AUTO_ALIGN_CLIPS_WAVEFORM_TRACK_AUTOMATIC. Constant names resolve against the
19
+ live Resolve object; integral numeric native values are also accepted. Omitted
20
+ options stay omitted, using native defaults. Unknown keys, invalid named
21
+ constants and malformed types are rejected. Native false stays success:false.
22
+
23
+ The callable method and 21.1 floor are checked. The compound action is registered
24
+ as a destructive MEDIUM-risk write in both classifier tables. Explicit dry-run
25
+ requests are refused before mutation. Granular annotations mark a destructive,
26
+ non-idempotent write.
27
+
28
+ ## Contributor validation and limits
29
+
30
+ Contributor-validated on macOS Studio 21.1.0.14, using synthetic speech and color
31
+ cards in a disposable 640x360/24 fps project. The two sources contain the same
32
+ speech but source timecodes differ by one second.
33
+
34
+ - Timecode fixture: deliberately placed at frames0/48, both wrappers moved the
35
+ selected video/audio to frames0/24, matching source timecodes.
36
+ - Waveform fixture with MIX: deliberately placed at frames0/24, both wrappers
37
+ moved all selected video/audio to frame0, matching identical speech content.
38
+ - For each mode, both wrappers' complete decoded ProRes video and PCM audio
39
+ matched an independently positioned manual reference exactly. This is full
40
+ rendered video/audio comparison, not just position readback.
41
+
42
+ Other audio track selections, dissimilar microphones, drift, variable frame rates,
43
+ long-form recordings and partly overlapping selections are not established by
44
+ this fixture. A failed chirp/video-only probe is not proof of a native bug.
45
+ Smart Switch is a separate operation and remains follow-up work.
46
+
47
+ `tests/live_resolve211_alignment.py OUTPUT_DIR` requires the disposable project
48
+ Codex Alignment Validation 20260909, with speech-red.mov and speech-blue.mov in
49
+ its root bin. Both are 24 fps color-card movies using the same synthetic spoken
50
+ recording; red timecode00:00:00:00 and blue timecode00:00:01:00. It creates manual,
51
+ compound and granular timelines for each mode, renders six movies and saves the
52
+ scratch project. Never substitute production media. Compare each mode's full
53
+ decoded video and audio against its manual reference. The helper tests also pin
54
+ ID ordering, malformed/missing input refusal, native failure and write gates.
@@ -48,11 +48,11 @@ Use the synthetic FFmpeg fixture commands in resolve211-native-transitions.md;
48
48
  never run the fixture against production media. Compare complete decoded frames
49
49
  from compound-before/compound-flattened/granular-before/granular-flattened.mov.
50
50
 
51
- Smart Switch and AutoAlignClips remain separate work. Native timecode alignment
52
- has positive position-readback evidence, but rendered/waveform acceptance remains
53
- open. Silent color-card Smart Switch returned false and is not a meaningful
54
- positive speaking-camera test. This contribution does not claim the entire
55
- multicam family completed.
51
+ Smart Switch remains separate work. Native timecode and waveform alignment now
52
+ have dedicated wrappers and rendered video/audio comparisons documented in
53
+ [alignment controls](resolve211-alignment.md). Silent color-card Smart Switch
54
+ returned false and is not a meaningful positive speaking-camera test. This
55
+ does not claim the entire multicam family completed.
56
56
 
57
57
  Do not infer angle order from clip_ids order. The wrapper forwards the requested
58
58
  source order, but this measurement establishes the selected angle and its
@@ -0,0 +1,58 @@
1
+ # Native Resolve 21.1 audio normalization
2
+
3
+ Compound `timeline normalize_audio_level` and granular
4
+ `normalize_timeline_audio_level` take explicit audio timeline `item_ids` and
5
+ optional `options`. IDs are resolved on the current timeline's audio tracks,
6
+ with no implicit linked-item expansion. Missing/duplicate IDs and malformed
7
+ options are refused before the native write. Native false stays success:false.
8
+
9
+ All NormalizeAudioOptions fields are supported:
10
+
11
+ - normalizationMode: native name from get_normalize_audio_modes.
12
+ - targetLevel: finite dBFS number, for example -6.
13
+ - targetLoudness: finite LKFS number, for example -23.
14
+ - setLevelMode: NORMALIZE_AUDIO_SET_LEVEL_RELATIVE or
15
+ NORMALIZE_AUDIO_SET_LEVEL_INDEPENDENT, or an integral native constant value.
16
+
17
+ Names resolve against the live Resolve object. Unknown option keys, invalid
18
+ named constants, booleans in numeric fields and non-finite values are rejected.
19
+ Omitted options stay omitted; Resolve supplies its native defaults. Mode names
20
+ are passed through rather than hard-coded into a stale list. Use the existing
21
+ mode reader for the current build. The wrapper does not invent range clamps or
22
+ turn a refused native normalization into success.
23
+
24
+ The action has a 21.1 callable-method floor, entries in both destructive/risk
25
+ tables (MEDIUM), destructive granular annotations, and explicit compound dry-run
26
+ refusal. Normalization changes project clip gain, not source audio files.
27
+
28
+ ## Contributor audio evidence
29
+
30
+ Contributor-validated on macOS Studio 21.1.0.14 in a disposable project using
31
+ 997 Hz synthetic stereo tones, with one source 12 dB quieter than the other.
32
+ Both actual community interfaces produced byte-identical decoded PCM per case.
33
+ Independent FFmpeg measurements of exported 24-bit/48 kHz WAVs:
34
+
35
+ | Requested case | Measured output |
36
+ |---|---|
37
+ | Sample Peak Program, relative, target -6 dBFS | Two clip segments peak -6.0/-18.0 dBFS; 12 dB difference preserved |
38
+ | Sample Peak Program, independent, target -6 dBFS | Both segments peak -6.0 dBFS |
39
+ | EBU R128, target -23 LKFS, peak setting-1 dBFS | Integrated -22.9 LUFS, within 0.1 LU of requested target |
40
+
41
+ These are exported-audio measurements, not just gain readback. They do not prove
42
+ all normalization modes, true-peak limiting on difficult signals, long-program
43
+ loudness, multichannel bus behavior, or different source formats.
44
+
45
+ `tests/live_resolve211_normalization.py OUTPUT_DIR` requires disposable project
46
+ Codex Normalization Validation 20260909 at 24 fps, with tone.wav and quiet.wav.
47
+ The script creates timelines/renders and saves that project. Generate fixtures
48
+ without production media:
49
+
50
+ ```sh
51
+ ffmpeg -f lavfi -i 'sine=frequency=997:sample_rate=48000:duration=4' \
52
+ -af 'pan=stereo|c0=c0|c1=c0' -c:a pcm_s24le tone.wav
53
+ ffmpeg -i tone.wav -af volume=-12dB -c:a pcm_s24le quiet.wav
54
+ ```
55
+
56
+ Measure each four-second segment with volumedetect and the loudness case with
57
+ ebur128. Compare full decoded PCM between interfaces. Pure tones are deliberate
58
+ meter fixtures; this does not substitute for representative-program QA.
package/install.py CHANGED
@@ -37,7 +37,7 @@ from src.utils.update_check import (
37
37
 
38
38
  # ─── Version ──────────────────────────────────────────────────────────────────
39
39
 
40
- VERSION = "2.220.0"
40
+ VERSION = "2.222.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
@@ -1543,7 +1543,7 @@ def verify_resolve_connection(python_path, api_path, lib_path):
1543
1543
 
1544
1544
  def print_banner():
1545
1545
  title = f"DaVinci Resolve MCP Server — Installer v{VERSION}"
1546
- subtitle = "36 compound · 373 full · 3 platforms"
1546
+ subtitle = "36 compound · 375 full · 3 platforms"
1547
1547
  print()
1548
1548
  print(bold(" ╔══════════════════════════════════════════════════════╗"))
1549
1549
  print(bold(f" ║{title:^54}║"))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "davinci-resolve-mcp",
3
- "version": "2.220.0",
3
+ "version": "2.222.0",
4
4
  "description": "NPM bootstrapper for the DaVinci Resolve MCP Server.",
5
5
  "license": "MIT",
6
6
  "author": "Samuel Gursky <samgursky@gmail.com>",
@@ -87,7 +87,7 @@ if not logging.getLogger().handlers:
87
87
  handlers=[logging.StreamHandler()],
88
88
  )
89
89
 
90
- VERSION = "2.220.0"
90
+ VERSION = "2.222.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()}")
@@ -1,4 +1,6 @@
1
1
  """Native Resolve 21.1 discovery and editing controls."""
2
+ from src.utils.resolve211_alignment import auto_align
3
+ from src.utils.resolve211_normalization import normalize_audio
2
4
  from src.utils.resolve211_blanking import validate_blanking
3
5
  from src.utils.resolve211_multicam import create_multicam, resolve_constant, GRADES
4
6
  from src.utils.resolve211_edits import validate_edit_options, validate_transition_options, transition_result
@@ -278,3 +280,27 @@ def set_timeline_item_use_timeline_for_output_blanking(use_timeline: bool, track
278
280
  if missing:
279
281
  return missing
280
282
  return {"success": bool(item.SetUseTimelineForOutputBlanking(use_timeline))}
283
+
284
+
285
+ @mcp.tool(annotations=DESTRUCTIVE_TOOL)
286
+ def normalize_timeline_audio_level(item_ids: list[str], options: dict | None = None) -> dict:
287
+ """Native 21.1 normalization of explicit audio timeline item IDs. Options normalizationMode, targetLevel (dBFS), targetLoudness (LKFS), setLevelMode; use get_normalize_audio_modes for names."""
288
+ _, timeline, error = _get_timeline()
289
+ if error:
290
+ return error
291
+ missing = _requires_method(timeline, "NormalizeAudioLevel", "21.1")
292
+ if missing:
293
+ return missing
294
+ return normalize_audio(get_resolve(), timeline, item_ids, {} if options is None else options)
295
+
296
+
297
+ @mcp.tool(annotations=DESTRUCTIVE_TOOL)
298
+ def auto_align_timeline_clips(item_ids: list[str], options: dict | None = None) -> dict:
299
+ """Native 21.1 alignment of current video/audio items by unique ID. Include linked audio AND video IDs to move both; selection is not expanded. Options SyncUsing and UseTrack accept documented constant names or integral native values."""
300
+ _, timeline, error = _get_timeline()
301
+ if error:
302
+ return error
303
+ missing = _requires_method(timeline, "AutoAlignClips", "21.1")
304
+ if missing:
305
+ return missing
306
+ return auto_align(get_resolve(), timeline, item_ids, {} if options is None else options)
@@ -34,7 +34,7 @@ from src.utils.update_check import start_background_update_check
34
34
  if __name__ == "__main__":
35
35
  try:
36
36
  start_background_update_check(VERSION, project_dir, logger)
37
- logger.info(f"Starting DaVinci Resolve MCP Server v{VERSION} (373 granular tools)")
37
+ logger.info(f"Starting DaVinci Resolve MCP Server v{VERSION} (375 granular tools)")
38
38
  run_fastmcp_stdio(mcp)
39
39
  except KeyboardInterrupt:
40
40
  logger.info("Server shutdown requested")
package/src/server.py CHANGED
@@ -8,10 +8,10 @@ Each tool groups related operations via an 'action' parameter.
8
8
 
9
9
  Usage:
10
10
  python src/server.py # Start the MCP server
11
- python src/server.py --full # Start the 373-tool granular server instead
11
+ python src/server.py --full # Start the 375-tool granular server instead
12
12
  """
13
13
 
14
- VERSION = "2.220.0"
14
+ VERSION = "2.222.0"
15
15
 
16
16
  import base64
17
17
  import os
@@ -44,6 +44,8 @@ for p in [current_dir, project_dir]:
44
44
 
45
45
  from src.utils.resolve211_multicam import create_multicam, resolve_constant, GRADES
46
46
  from src.utils.resolve211_blanking import validate_blanking
47
+ from src.utils.resolve211_alignment import auto_align
48
+ from src.utils.resolve211_normalization import normalize_audio
47
49
  from src.utils.resolve211_edits import validate_edit_options, validate_transition_options, transition_result
48
50
 
49
51
  # Platform-specific Resolve paths
@@ -25053,7 +25055,7 @@ def edit_engine(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[st
25053
25055
 
25054
25056
 
25055
25057
  _TIMELINE_ACTIONS = [
25056
- "set_output_blanking", "get_normalize_audio_modes", "get_output_blanking",
25058
+ "set_output_blanking", "normalize_audio_level", "auto_align_clips", "get_normalize_audio_modes", "get_output_blanking",
25057
25059
  # Offline authoring — served without a Resolve connection, above the _check() gate.
25058
25060
  "author_offline", "offline_fallback_capabilities",
25059
25061
  "list", "get_current", "set_current", "get_name", "set_name", "get_start_frame",
@@ -25103,6 +25105,8 @@ def timeline(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str,
25103
25105
 
25104
25106
  Actions:
25105
25107
  set_output_blanking(options) -> {success} — native 21.1 pixel coordinates.
25108
+ normalize_audio_level(item_ids, options?) -> {success} — native 21.1 normalization; audio timeline item IDs.
25109
+ auto_align_clips(item_ids, options?) -> {success} — native 21.1 timecode/waveform alignment; include both video/audio IDs to move linked pairs.
25106
25110
  get_normalize_audio_modes() -> {modes} — documented on Resolve 21.1+.
25107
25111
  get_output_blanking() -> {blanking} — documented on Resolve 21.1+. Pixel coordinates; empty on a clip inheriting timeline blanking.
25108
25112
  list() -> {timelines}
@@ -25421,6 +25425,18 @@ def timeline(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str,
25421
25425
  if not tl:
25422
25426
  return _err("No current timeline")
25423
25427
 
25428
+ if action == "auto_align_clips":
25429
+ missing = _requires_method(tl, "AutoAlignClips", "21.1")
25430
+ if missing:
25431
+ return missing
25432
+ return auto_align(get_resolve(), tl, p.get("item_ids"), {} if p.get("options") is None else p["options"])
25433
+
25434
+ if action == "normalize_audio_level":
25435
+ missing = _requires_method(tl, "NormalizeAudioLevel", "21.1")
25436
+ if missing:
25437
+ return missing
25438
+ return normalize_audio(get_resolve(), tl, p.get("item_ids"), {} if p.get("options") is None else p["options"])
25439
+
25424
25440
  if action == "set_output_blanking":
25425
25441
  error = validate_blanking(p.get("options"))
25426
25442
  if error:
@@ -32721,9 +32737,9 @@ if __name__ == "__main__":
32721
32737
  start_background_update_check(VERSION, project_dir, logger, env=_setup_update_env())
32722
32738
  _install_threaded_tool_dispatch(mcp)
32723
32739
 
32724
- # Support --full flag to run the 373-tool granular server instead
32740
+ # Support --full flag to run the 375-tool granular server instead
32725
32741
  if "--full" in sys.argv:
32726
- logger.info("Starting full 373-tool granular server...")
32742
+ logger.info("Starting full 375-tool granular server...")
32727
32743
  sys.argv = [arg for arg in sys.argv if arg != "--full"]
32728
32744
  from src.granular import mcp as granular_mcp
32729
32745
 
@@ -88,6 +88,8 @@ DESTRUCTIVE_ACTIONS_BY_TOOL: Dict[str, FrozenSet[str]] = {
88
88
  }),
89
89
  "timeline": frozenset({
90
90
  "set_output_blanking",
91
+ "normalize_audio_level",
92
+ "auto_align_clips",
91
93
  "delete_clips",
92
94
  "move_clips",
93
95
  "duplicate_clips",
@@ -275,10 +275,13 @@ class RiskClassificationHook(LifecycleHook):
275
275
  # RippleTimeline true, moves every clip after it; `set_fades` rewrites
276
276
  # how the clip's edges render. Existing content altered, not deleted.
277
277
  ("media_pool", "create_multicam_clip"),
278
+ ("timeline", "auto_align_clips"),
278
279
  ("timeline_item", "flatten_multicam"),
279
280
  ("timeline", "set_output_blanking"),
280
281
  ("timeline_item", "set_output_blanking"),
281
282
  ("timeline_item", "set_use_timeline_for_output_blanking"),
283
+
284
+ ("timeline", "normalize_audio_level"),
282
285
  ("timeline_item", "add_transition"),
283
286
  ("timeline_item", "set_speed"),
284
287
  ("timeline_item", "set_fades"),
@@ -0,0 +1,32 @@
1
+ """Native alignment with strict ID resolution before any timeline mutation."""
2
+ from src.utils.resolve211_multicam import resolve_constant
3
+
4
+ OPTIONS = {
5
+ 'SyncUsing': ('AUTO_ALIGN_CLIPS_USING_TIMECODE','AUTO_ALIGN_CLIPS_USING_WAVEFORM'),
6
+ 'UseTrack': ('AUTO_ALIGN_CLIPS_WAVEFORM_TRACK_MIX','AUTO_ALIGN_CLIPS_WAVEFORM_TRACK_AUTOMATIC'),
7
+ }
8
+
9
+
10
+ def auto_align(r, timeline, item_ids, options):
11
+ if not isinstance(item_ids,list) or not item_ids or any(not isinstance(i,str) or not i for i in item_ids):
12
+ return {'error':'item_ids must be a non-empty list of timeline item unique IDs'}
13
+ if len(set(item_ids))!=len(item_ids):
14
+ return {'error':'item_ids must not contain duplicates'}
15
+ if not isinstance(options,dict) or set(options)-set(OPTIONS):
16
+ return {'error':'options must contain only SyncUsing and/or UseTrack'}
17
+ normalized={}
18
+ for key,value in options.items():
19
+ normalized[key],error=resolve_constant(r,value,OPTIONS[key])
20
+ if error:
21
+ return {'error':key+': '+error}
22
+ wanted=set(item_ids)
23
+ found={}
24
+ for track_type in ('video','audio'):
25
+ for index in range(1,timeline.GetTrackCount(track_type)+1):
26
+ for item in timeline.GetItemListInTrack(track_type,index) or []:
27
+ uid=item.GetUniqueId()
28
+ if uid in wanted:
29
+ found[uid]=item
30
+ if any(uid not in found for uid in item_ids):
31
+ return {'error':'One or more item_ids were not found in current video/audio tracks; no alignment performed'}
32
+ return {'success':bool(timeline.AutoAlignClips([found[uid] for uid in item_ids],normalized))}
@@ -0,0 +1,42 @@
1
+ """Native normalization: validate explicit audio-item selection before writes."""
2
+ import math
3
+
4
+
5
+ def finite_number(value):
6
+ try:
7
+ return type(value) in (int,float) and math.isfinite(value)
8
+ except OverflowError:
9
+ return False
10
+
11
+
12
+ def normalize_audio(r, timeline, item_ids, options):
13
+ if not isinstance(item_ids,list) or not item_ids or any(not isinstance(i,str) or not i for i in item_ids):
14
+ return {'error':'item_ids must be a non-empty list of audio timeline item unique IDs'}
15
+ if len(set(item_ids))!=len(item_ids):
16
+ return {'error':'item_ids must not contain duplicates'}
17
+ if not isinstance(options,dict) or set(options)-{'normalizationMode','targetLevel','targetLoudness','setLevelMode'}:
18
+ return {'error':'Unknown normalization options or non-dictionary options'}
19
+ normalized=dict(options)
20
+ for key,value in options.items():
21
+ if key=='normalizationMode':
22
+ if not isinstance(value,str) or not value.strip():
23
+ return {'error':'normalizationMode must be a non-empty native mode name'}
24
+ elif key=='setLevelMode':
25
+ if isinstance(value,str):
26
+ if value not in ('NORMALIZE_AUDIO_SET_LEVEL_RELATIVE','NORMALIZE_AUDIO_SET_LEVEL_INDEPENDENT'):
27
+ return {'error':'Unknown setLevelMode constant'}
28
+ value=getattr(r,value,None)
29
+ if not finite_number(value) or int(value)!=value:
30
+ return {'error':'setLevelMode must be a documented constant name or integral native value'}
31
+ normalized[key]=value
32
+ elif not finite_number(value):
33
+ return {'error':key+' must be a finite number'}
34
+ wanted=set(item_ids);found={}
35
+ for index in range(1,timeline.GetTrackCount('audio')+1):
36
+ for item in timeline.GetItemListInTrack('audio',index) or []:
37
+ uid=item.GetUniqueId()
38
+ if uid in wanted:
39
+ found[uid]=item
40
+ if any(uid not in found for uid in item_ids):
41
+ return {'error':'One or more IDs were not found on audio tracks; no normalization performed'}
42
+ return {'success':bool(timeline.NormalizeAudioLevel([found[uid] for uid in item_ids],normalized))}
@@ -225,11 +225,14 @@ _EVIDENCE_GATES: List[Dict[str, Any]] = [
225
225
  # disagree, which is what keeps the two from drifting apart again.
226
226
 
227
227
  CODE_FLOORS: Dict[str, str] = {
228
+ "Timeline.AutoAlignClips": "21.1",
228
229
  "MediaPool.CreateMulticamClip": "21.1",
229
230
  "TimelineItem.FlattenMulticam": "21.1",
230
231
  "Timeline.SetOutputBlanking": "21.1",
231
232
  "TimelineItem.SetOutputBlanking": "21.1",
232
233
  "TimelineItem.SetUseTimelineForOutputBlanking": "21.1",
234
+
235
+ "Timeline.NormalizeAudioLevel": "21.1",
233
236
  "TimelineItem.AddTransition": "21.1",
234
237
  "TimelineItem.SetSpeed": "21.1",
235
238
  "TimelineItem.SetFades": "21.1",