davinci-resolve-mcp 2.220.0 → 2.221.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 +46 -0
- package/README.md +4 -4
- package/README.zh-CN.md +5 -5
- package/docs/SKILL.md +3 -1
- package/docs/contributing.md +1 -1
- package/docs/install.md +2 -2
- package/docs/reference/api-coverage.md +3 -1
- package/docs/reference/resolve211-normalization.md +58 -0
- package/install.py +2 -2
- package/package.json +1 -1
- package/src/granular/common.py +1 -1
- package/src/granular/resolve_211.py +13 -0
- package/src/resolve_mcp_server.py +1 -1
- package/src/server.py +13 -5
- package/src/utils/destructive_hook.py +1 -0
- package/src/utils/execution_lifecycle.py +2 -0
- package/src/utils/resolve211_normalization.py +42 -0
- package/src/utils/resolve_versions.py +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,52 @@
|
|
|
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.221.0 — native Resolve 21.1 audio level normalization
|
|
6
|
+
|
|
7
|
+
Contributed by @legionsound (#214), live-validated on Studio 21.1.0.14.
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **`timeline normalize_audio_level`**, with the granular twin
|
|
12
|
+
`normalize_timeline_audio_level`, calling native 21.1 `NormalizeAudioLevel`
|
|
13
|
+
on explicit audio timeline item IDs. All four `NormalizeAudioOptions` fields
|
|
14
|
+
are supported — `normalizationMode`, `targetLevel` in dBFS, `targetLoudness`
|
|
15
|
+
in LKFS and `setLevelMode` — with either documented constant names or
|
|
16
|
+
integral native values. Every ID resolves before anything is written;
|
|
17
|
+
duplicate or missing IDs and malformed options are refused with nothing
|
|
18
|
+
changed. A native `false` stays `false`, and native defaults stay native
|
|
19
|
+
defaults rather than being pre-filled by the wrapper.
|
|
20
|
+
- Registered in **both** write tables with a 21.1 callable-method floor,
|
|
21
|
+
destructive granular annotations and compound dry-run refusal tests. Tool
|
|
22
|
+
count 373 → 374.
|
|
23
|
+
- **Source audio files are untouched** — this writes clip levels on the
|
|
24
|
+
timeline, not the media on disk.
|
|
25
|
+
|
|
26
|
+
### Validation
|
|
27
|
+
|
|
28
|
+
- Full suite green: 3,461 passed, 1 skipped. `timeline.normalize_audio_level`
|
|
29
|
+
probed directly: MEDIUM / destructive / recognised in the classifier, and
|
|
30
|
+
present in the destructive registry.
|
|
31
|
+
- Live evidence is @legionsound's on Studio 21.1.0.14, and it is measured from
|
|
32
|
+
the **exported audio** rather than from a readback — independent FFmpeg
|
|
33
|
+
analysis of WAVs the wrappers actually produced: relative peak normalization
|
|
34
|
+
to −6 dBFS preserved the source 12 dB difference (−6.0 / −18.0 dBFS),
|
|
35
|
+
independent peak normalization measured −6.0 / −6.0 dBFS, and an EBU R128
|
|
36
|
+
target of −23 LKFS measured −22.9 LUFS, within 0.1 LU. Complete decoded PCM
|
|
37
|
+
was identical between both interfaces in every case. Not reproduced here;
|
|
38
|
+
this machine is Studio 19.1.3.7, below the 21.1 floor.
|
|
39
|
+
- **Not claimed**: other normalization modes, difficult true-peak limiting,
|
|
40
|
+
multichannel routing, long programs and other source formats.
|
|
41
|
+
|
|
42
|
+
### Changed
|
|
43
|
+
|
|
44
|
+
- Adapted on merge, the same way #213 was. The branch was rebased onto
|
|
45
|
+
v2.219.0, so its 370 → 371 count bump and every generated agent-rule file
|
|
46
|
+
collided with the 373 that output blanking had landed. Counts resolved to
|
|
47
|
+
**374**, confirmed independently by the agent-rule generator, and the
|
|
48
|
+
generated files regenerated rather than hand-merged. No behaviour changed in
|
|
49
|
+
the adaptation.
|
|
50
|
+
|
|
5
51
|
## What's New in v2.220.0 — native Resolve 21.1 output blanking, timeline and clip
|
|
6
52
|
|
|
7
53
|
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
|
-
[](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
|
|
6
6
|
[](https://www.npmjs.com/package/davinci-resolve-mcp)
|
|
7
7
|
[](docs/reference/api-coverage.md)
|
|
8
|
-
[-blue.svg)](#server-modes)
|
|
9
9
|
[-18%20tools-blueviolet.svg)](#server-modes)
|
|
10
10
|
[](docs/reference/api-coverage.md#test-results)
|
|
11
11
|
[](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` |
|
|
136
|
+
| Full / granular | `src/server.py --full` or `src/resolve_mcp_server.py` | 374 | 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 / **
|
|
368
|
+
| MCP Tools | **36** compound / **374** 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
|
-
[](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
|
|
6
6
|
[](https://www.npmjs.com/package/davinci-resolve-mcp)
|
|
7
7
|
[](docs/reference/api-coverage.md)
|
|
8
|
-
[-blue.svg)](#服务器模式)
|
|
9
9
|
[-18%20tools-blueviolet.svg)](#服务器模式)
|
|
10
10
|
[](docs/reference/api-coverage.md#test-results)
|
|
11
11
|
[](https://www.blackmagicdesign.com/products/davinciresolve)
|
|
12
12
|
[](https://www.python.org/downloads/)
|
|
13
13
|
[](https://opensource.org/licenses/MIT)
|
|
14
14
|
|
|
15
|
-
> 本翻译对应 v2.
|
|
15
|
+
> 本翻译对应 v2.221.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` |
|
|
91
|
+
| Full / granular(细粒度) | `src/server.py --full` 或 `src/resolve_mcp_server.py` | 374 | 想要"一个 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** 复合 / **
|
|
229
|
+
| MCP 工具 | **36** 复合 / **374** 细粒度(实时服务器) |
|
|
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` |
|
|
348
|
+
| Granular (full) | `src/server.py --full` | 374 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,5 @@ 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.
|
package/docs/contributing.md
CHANGED
|
@@ -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 —
|
|
67
|
+
│ ├── resolve_mcp_server.py # Thin full-server entrypoint — 374 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` |
|
|
147
|
+
| **Full** | `src/resolve_mcp_server.py` | 374 | 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
|
|
162
|
+
python src/server.py --full # Launch full 374-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 **
|
|
28
|
+
provides **374 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,5 @@ 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.
|
|
@@ -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.
|
|
40
|
+
VERSION = "2.221.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 ·
|
|
1546
|
+
subtitle = "36 compound · 374 full · 3 platforms"
|
|
1547
1547
|
print()
|
|
1548
1548
|
print(bold(" ╔══════════════════════════════════════════════════════╗"))
|
|
1549
1549
|
print(bold(f" ║{title:^54}║"))
|
package/package.json
CHANGED
package/src/granular/common.py
CHANGED
|
@@ -87,7 +87,7 @@ if not logging.getLogger().handlers:
|
|
|
87
87
|
handlers=[logging.StreamHandler()],
|
|
88
88
|
)
|
|
89
89
|
|
|
90
|
-
VERSION = "2.
|
|
90
|
+
VERSION = "2.221.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,5 @@
|
|
|
1
1
|
"""Native Resolve 21.1 discovery and editing controls."""
|
|
2
|
+
from src.utils.resolve211_normalization import normalize_audio
|
|
2
3
|
from src.utils.resolve211_blanking import validate_blanking
|
|
3
4
|
from src.utils.resolve211_multicam import create_multicam, resolve_constant, GRADES
|
|
4
5
|
from src.utils.resolve211_edits import validate_edit_options, validate_transition_options, transition_result
|
|
@@ -278,3 +279,15 @@ def set_timeline_item_use_timeline_for_output_blanking(use_timeline: bool, track
|
|
|
278
279
|
if missing:
|
|
279
280
|
return missing
|
|
280
281
|
return {"success": bool(item.SetUseTimelineForOutputBlanking(use_timeline))}
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
@mcp.tool(annotations=DESTRUCTIVE_TOOL)
|
|
285
|
+
def normalize_timeline_audio_level(item_ids: list[str], options: dict | None = None) -> dict:
|
|
286
|
+
"""Native 21.1 normalization of explicit audio timeline item IDs. Options normalizationMode, targetLevel (dBFS), targetLoudness (LKFS), setLevelMode; use get_normalize_audio_modes for names."""
|
|
287
|
+
_, timeline, error = _get_timeline()
|
|
288
|
+
if error:
|
|
289
|
+
return error
|
|
290
|
+
missing = _requires_method(timeline, "NormalizeAudioLevel", "21.1")
|
|
291
|
+
if missing:
|
|
292
|
+
return missing
|
|
293
|
+
return normalize_audio(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} (
|
|
37
|
+
logger.info(f"Starting DaVinci Resolve MCP Server v{VERSION} (374 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
|
|
11
|
+
python src/server.py --full # Start the 374-tool granular server instead
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
|
-
VERSION = "2.
|
|
14
|
+
VERSION = "2.221.0"
|
|
15
15
|
|
|
16
16
|
import base64
|
|
17
17
|
import os
|
|
@@ -44,6 +44,7 @@ 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_normalization import normalize_audio
|
|
47
48
|
from src.utils.resolve211_edits import validate_edit_options, validate_transition_options, transition_result
|
|
48
49
|
|
|
49
50
|
# Platform-specific Resolve paths
|
|
@@ -25053,7 +25054,7 @@ def edit_engine(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[st
|
|
|
25053
25054
|
|
|
25054
25055
|
|
|
25055
25056
|
_TIMELINE_ACTIONS = [
|
|
25056
|
-
"set_output_blanking", "get_normalize_audio_modes", "get_output_blanking",
|
|
25057
|
+
"set_output_blanking", "normalize_audio_level", "get_normalize_audio_modes", "get_output_blanking",
|
|
25057
25058
|
# Offline authoring — served without a Resolve connection, above the _check() gate.
|
|
25058
25059
|
"author_offline", "offline_fallback_capabilities",
|
|
25059
25060
|
"list", "get_current", "set_current", "get_name", "set_name", "get_start_frame",
|
|
@@ -25103,6 +25104,7 @@ def timeline(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str,
|
|
|
25103
25104
|
|
|
25104
25105
|
Actions:
|
|
25105
25106
|
set_output_blanking(options) -> {success} — native 21.1 pixel coordinates.
|
|
25107
|
+
normalize_audio_level(item_ids, options?) -> {success} — native 21.1 normalization; audio timeline item IDs.
|
|
25106
25108
|
get_normalize_audio_modes() -> {modes} — documented on Resolve 21.1+.
|
|
25107
25109
|
get_output_blanking() -> {blanking} — documented on Resolve 21.1+. Pixel coordinates; empty on a clip inheriting timeline blanking.
|
|
25108
25110
|
list() -> {timelines}
|
|
@@ -25421,6 +25423,12 @@ def timeline(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str,
|
|
|
25421
25423
|
if not tl:
|
|
25422
25424
|
return _err("No current timeline")
|
|
25423
25425
|
|
|
25426
|
+
if action == "normalize_audio_level":
|
|
25427
|
+
missing = _requires_method(tl, "NormalizeAudioLevel", "21.1")
|
|
25428
|
+
if missing:
|
|
25429
|
+
return missing
|
|
25430
|
+
return normalize_audio(get_resolve(), tl, p.get("item_ids"), {} if p.get("options") is None else p["options"])
|
|
25431
|
+
|
|
25424
25432
|
if action == "set_output_blanking":
|
|
25425
25433
|
error = validate_blanking(p.get("options"))
|
|
25426
25434
|
if error:
|
|
@@ -32721,9 +32729,9 @@ if __name__ == "__main__":
|
|
|
32721
32729
|
start_background_update_check(VERSION, project_dir, logger, env=_setup_update_env())
|
|
32722
32730
|
_install_threaded_tool_dispatch(mcp)
|
|
32723
32731
|
|
|
32724
|
-
# Support --full flag to run the
|
|
32732
|
+
# Support --full flag to run the 374-tool granular server instead
|
|
32725
32733
|
if "--full" in sys.argv:
|
|
32726
|
-
logger.info("Starting full
|
|
32734
|
+
logger.info("Starting full 374-tool granular server...")
|
|
32727
32735
|
sys.argv = [arg for arg in sys.argv if arg != "--full"]
|
|
32728
32736
|
from src.granular import mcp as granular_mcp
|
|
32729
32737
|
|
|
@@ -279,6 +279,8 @@ class RiskClassificationHook(LifecycleHook):
|
|
|
279
279
|
("timeline", "set_output_blanking"),
|
|
280
280
|
("timeline_item", "set_output_blanking"),
|
|
281
281
|
("timeline_item", "set_use_timeline_for_output_blanking"),
|
|
282
|
+
|
|
283
|
+
("timeline", "normalize_audio_level"),
|
|
282
284
|
("timeline_item", "add_transition"),
|
|
283
285
|
("timeline_item", "set_speed"),
|
|
284
286
|
("timeline_item", "set_fades"),
|
|
@@ -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))}
|
|
@@ -230,6 +230,8 @@ CODE_FLOORS: Dict[str, str] = {
|
|
|
230
230
|
"Timeline.SetOutputBlanking": "21.1",
|
|
231
231
|
"TimelineItem.SetOutputBlanking": "21.1",
|
|
232
232
|
"TimelineItem.SetUseTimelineForOutputBlanking": "21.1",
|
|
233
|
+
|
|
234
|
+
"Timeline.NormalizeAudioLevel": "21.1",
|
|
233
235
|
"TimelineItem.AddTransition": "21.1",
|
|
234
236
|
"TimelineItem.SetSpeed": "21.1",
|
|
235
237
|
"TimelineItem.SetFades": "21.1",
|