davinci-resolve-mcp 4.9.0 → 4.9.1

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,48 @@
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 v4.9.1 — a rejected audio level write now says why, and what to do instead
6
+
7
+ ### Changed
8
+
9
+ - **`safe_set_audio_properties` and `timeline_item set_audio` explain a
10
+ refused Volume / Pan / EQ write.** Resolve's scripting API has no write path
11
+ for audio clip or track level — `SetProperty('Volume'/'Level'/'Gain')`
12
+ returned `False` when measured live on 21.0.0, and `'Pan'` is the *video* transform
13
+ key, so it returns `True` while the audio pan does not move. A caller who saw
14
+ `{"write": false}` (or a `Pan` that "succeeded" and changed nothing) had no
15
+ way to tell "bad value" from "this cannot be written from the API at all" —
16
+ and the second is a different task. Both actions now attach a
17
+ `known_limitation` block when a level or EQ write fails, and on every `Pan`
18
+ write (its success is the misleading case): the
19
+ `api_truth` ledger entry plus the concrete ways around it (bake the gain into
20
+ a rendered copy of the source with ffmpeg; or save the mix once as a
21
+ Fairlight preset and apply it per-timeline with
22
+ `project_settings apply_fairlight_preset`, on Resolve 20.2.2+ only; the
23
+ preset methods are absent on 19.x). `AudioSyncOffset` writes are
24
+ unaffected — they work, and are not flagged. The legacy granular
25
+ `set_timeline_item_audio` returns the same guidance as its failure string.
26
+ Contributed by @youssefm3208-jpg (#279).
27
+
28
+ ### Changed on landing
29
+
30
+ - A `Volume` / `Level` / `Gain` / EQ write is flagged only when Resolve
31
+ refused it, so a build that honours the write is not told it is impossible.
32
+ - The Fairlight-preset workaround names its Resolve 20.2.2 floor.
33
+
34
+ ### Tests
35
+
36
+ - `tests/test_audio_fairlight_probe.py`: a refused Volume write carries the
37
+ ledger entry and workarounds; a Pan write is flagged even when it returns
38
+ True; `AudioSyncOffset` is not flagged; an honoured Volume write is not
39
+ flagged; the preset workaround states its version floor.
40
+
41
+ ### Validation
42
+
43
+ - Response-shape change only; no Resolve scripting call changed, so no live
44
+ run was required. The Volume/Pan behaviour is the `api_truth` ledger's live
45
+ measurement on 21.0.0.
46
+
5
47
  ## What's New in v4.9.0 — bounded Media Pool import
6
48
 
7
49
  ### Added
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  English | [简体中文](README.zh-CN.md)
4
4
 
5
- [![Version](https://img.shields.io/badge/version-4.9.0-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-4.9.1-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
8
  [![Tools](https://img.shields.io/badge/MCP%20Tools-37%20(389%20full)-blue.svg)](#server-modes)
package/README.zh-CN.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [English](README.md) | 简体中文
4
4
 
5
- [![Version](https://img.shields.io/badge/version-4.9.0-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-4.9.1-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
8
  [![Tools](https://img.shields.io/badge/MCP%20Tools-37%20(389%20full)-blue.svg)](#服务器模式)
@@ -12,7 +12,7 @@
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
- > 本翻译对应 v4.9.0 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
15
+ > 本翻译对应 v4.9.1 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
16
16
 
17
17
  一个 Model Context Protocol (MCP) 服务器,让 AI 助手通过官方脚本 API 控制 DaVinci Resolve Studio(达芬奇)。它提供完整的 API 覆盖,外加带护栏的工作流助手,涵盖剪辑、媒体池整理、渲染设置、审阅标记、调色、Fusion、Fairlight、项目生命周期任务、扩展开发,以及不碰源媒体的媒体分析。
18
18
 
package/docs/SKILL.md CHANGED
@@ -1787,6 +1787,10 @@ helpers:
1787
1787
  - `probe_audio_track(track_index?)`
1788
1788
  - `probe_audio_item(track_type?, track_index?, item_index?)`
1789
1789
  - `safe_set_audio_properties(properties, restore?, dry_run?, track_type?, track_index?, item_index?)`
1790
+ — Volume / Pan / EQ writes are not honoured by Resolve's API; a request for
1791
+ any of them returns a `known_limitation` block with the workaround (bake gain
1792
+ into a rendered copy, or apply a Fairlight preset). `AudioSyncOffset` writes
1793
+ do work.
1790
1794
  - `audio_mix_capability_report(...)`
1791
1795
  - `voice_isolation_capabilities(track_index?, track_type?, item_index?)`
1792
1796
  - `audio_mapping_report(clip_ids?)`
@@ -1911,7 +1915,9 @@ Key actions:
1911
1915
  - `get_transform` / `set_transform(Pan?, Tilt?, ZoomX?, ZoomY?, RotationAngle?, ...)`
1912
1916
  - `get_crop` / `set_crop(CropLeft?, CropRight?, CropTop?, CropBottom?, ...)`
1913
1917
  - `get_composite` / `set_composite(Opacity?, CompositeMode?)`
1914
- - `get_audio` / `set_audio(Volume?, Pan?, AudioSyncOffset?)`
1918
+ - `get_audio` / `set_audio(Volume?, Pan?, AudioSyncOffset?)` — Resolve ignores
1919
+ Volume / Pan / EQ writes on audio (returns a `known_limitation` block); only
1920
+ `AudioSyncOffset` / `AudioSyncOffsetIsManual` take effect
1915
1921
  - `get_voice_isolation_state` / `set_voice_isolation_state(state)` — Resolve
1916
1922
  20.1+; audio timeline items only
1917
1923
  - `get_keyframes(property)`, `add_keyframe(property, frame, value)`,
package/install.py CHANGED
@@ -37,7 +37,7 @@ from src.utils.update_check import (
37
37
 
38
38
  # ─── Version ──────────────────────────────────────────────────────────────────
39
39
 
40
- VERSION = "4.9.0"
40
+ VERSION = "4.9.1"
41
41
  # Only hard floor: mcp[cli] requires Python 3.10+. There is no upper bound —
42
42
  # Resolve's scripting bridge loads into newer interpreters on recent builds
43
43
  # (Python 3.14 verified against Resolve Studio 20.3.2). Older Resolve builds
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "davinci-resolve-mcp",
3
- "version": "4.9.0",
3
+ "version": "4.9.1",
4
4
  "description": "NPM bootstrapper for the DaVinci Resolve MCP Server.",
5
5
  "license": "MIT",
6
6
  "author": "Samuel Gursky <samgursky@gmail.com>",
@@ -91,7 +91,7 @@ if not logging.getLogger().handlers:
91
91
  handlers=[logging.StreamHandler()],
92
92
  )
93
93
 
94
- VERSION = "4.9.0"
94
+ VERSION = "4.9.1"
95
95
  logger = logging.getLogger("davinci-resolve-mcp")
96
96
  logger.info(f"Starting DaVinci Resolve MCP Server v{VERSION}")
97
97
  logger.info(f"Detected platform: {get_platform()}")
@@ -716,7 +716,17 @@ def set_timeline_item_audio(timeline_item_id: str,
716
716
 
717
717
  return f"Successfully set {' and '.join(changes)} for timeline item '{timeline_item.GetName()}'"
718
718
  else:
719
- return f"Failed to set some audio properties for timeline item '{timeline_item.GetName()}'"
719
+ return (
720
+ f"Failed to set audio properties for timeline item "
721
+ f"'{timeline_item.GetName()}'. Resolve's scripting API has no "
722
+ "write path for audio level, pan, or EQ — SetProperty covers "
723
+ "the video transform only, so 'Volume'/'Gain' return False and "
724
+ "'Pan' moves the video transform, not the audio pan. Work "
725
+ "around it by baking the gain into a rendered copy of the "
726
+ "source (ffmpeg volume=NdB) and importing that, or (Resolve "
727
+ "20.2.2+) by saving a Fairlight preset in the UI and applying "
728
+ "it with project_settings apply_fairlight_preset."
729
+ )
720
730
  except Exception as e:
721
731
  return f"Error setting timeline item audio properties: {str(e)}"
722
732
 
package/src/server.py CHANGED
@@ -11,7 +11,7 @@ Usage:
11
11
  python src/server.py --full # Start the 377-tool granular server instead
12
12
  """
13
13
 
14
- VERSION = "4.9.0"
14
+ VERSION = "4.9.1"
15
15
 
16
16
  import base64
17
17
  import os
@@ -803,10 +803,13 @@ edits audio files with no Resolve open. Plan/measure offline, apply live.
803
803
  - Offline `audio`: split (silence/TC/intervals) / trim / convert (needs ffmpeg on
804
804
  PATH — GPL, not bundled). Align/loudness-measure not yet vendored.
805
805
 
806
- Timeline audio SetProperty (e.g. Volume) can return false for some item types;
807
- the public API exposes no Fairlight automation curves — use fairlight for bus
808
- structure only. The offline audio ops write NEW files to scratch, never over
809
- source (AGENTS.md).
806
+ Timeline audio SetProperty for level/pan/EQ does not work — 'Volume'/'Gain'
807
+ return false and 'Pan' writes the video transform, not the audio pan; the
808
+ public API exposes no Fairlight faders or automation curves. safe_set_audio_properties
809
+ and timeline_item set_audio return a `known_limitation` block pointing at the
810
+ fix: bake the gain into a rendered copy of the source, or apply a saved
811
+ Fairlight preset. Use fairlight for bus structure only. The offline audio ops
812
+ write NEW files to scratch, never over source (AGENTS.md).
810
813
 
811
814
  Depth: docs/kernels/audio-fairlight-kernel.md."""
812
815
 
@@ -8807,7 +8810,7 @@ def _audio_capabilities():
8807
8810
  "partially_supported": {
8808
8811
  "voice_isolation": "Track/item voice isolation depends on Resolve version, license, page state, and audio content.",
8809
8812
  "transcription_subtitles": "Transcription and subtitle generation can be asynchronous and may require installed AI components.",
8810
- "audio_property_writes": "Some item types expose audio properties as read-only or reject writes despite returning readable values.",
8813
+ "audio_property_writes": "Level/pan/EQ writes are not honoured: SetProperty('Volume'/'Gain') returns False and 'Pan' writes the video transform. AudioSyncOffset writes do work. safe_set_audio_properties returns a known_limitation block with the bake / Fairlight-preset workaround.",
8811
8814
  "auto_sync": "AutoSyncAudio depends on media content, channel layout, and selected sync settings.",
8812
8815
  },
8813
8816
  "unsupported": {
@@ -8899,6 +8902,61 @@ def _probe_audio_item(tl, p: Dict[str, Any]):
8899
8902
  return _timeline_item_audio_snapshot(item)
8900
8903
 
8901
8904
 
8905
+ _AUDIO_LEVEL_KEYS = {"Volume", "Level", "Gain", "AudioVolume", "Pan", "EQEnable", "EQEnabled"}
8906
+
8907
+
8908
+ def _audio_write_limitation(written: Dict[str, Any]) -> Optional[Dict[str, Any]]:
8909
+ """Guidance for an audio level/pan/EQ write that Resolve will not honour.
8910
+
8911
+ `written` maps each requested key to whether SetProperty returned True.
8912
+ `SetProperty` on a TimelineItem covers the *video* transform only.
8913
+ 'Volume'/'Level'/'Gain' returned False when measured live on 21.0.0, and
8914
+ 'Pan' is the video-transform key — it returns True while the audio pan
8915
+ stays put. A caller who sees `write: false` (or a Pan write that
8916
+ "succeeds" and changes nothing) has hit a missing feature, not a bad
8917
+ value, and the way around it is a different task: bake the gain into the
8918
+ media, or drive Fairlight. Level/EQ keys are flagged only when the write
8919
+ failed, so a build that honours them is not contradicted; Pan is always
8920
+ flagged, because its success is the misleading case.
8921
+ """
8922
+ hit = sorted({
8923
+ k for k, ok in written.items()
8924
+ if k in _AUDIO_LEVEL_KEYS and (k == "Pan" or not ok)
8925
+ })
8926
+ if not hit:
8927
+ return None
8928
+ entry = next(iter(lookup_api_truth("Fairlight audio levels")), None)
8929
+ out = {
8930
+ "keys": hit,
8931
+ "reason": "no_api_write_path",
8932
+ "explanation": (
8933
+ "Resolve's scripting API cannot set audio clip or track level, pan, "
8934
+ "EQ, or automation. SetProperty writes the video transform only: "
8935
+ "'Volume'/'Level'/'Gain' return False, and 'Pan' is the video "
8936
+ "transform key so it returns True while the audio pan is unchanged."
8937
+ ),
8938
+ "workarounds": [
8939
+ "Bake the level into a rendered copy of the source (ffmpeg "
8940
+ "volume=NdB, plus afade / atrim for fades and trims) and import "
8941
+ "that — the level is then part of the file.",
8942
+ "For a repeatable whole mix (Resolve 20.2.2+; the preset methods "
8943
+ "do not exist on 19.x), save it once as a Fairlight preset in "
8944
+ "the Resolve UI, then apply it per timeline with "
8945
+ "project_settings apply_fairlight_preset "
8946
+ "(names from resolve_control get_fairlight_presets).",
8947
+ "Set individual faders / pan / EQ on the Fairlight page by hand.",
8948
+ ],
8949
+ "ledger_verified_on": _API_TRUTH_VERIFIED_ON,
8950
+ }
8951
+ if entry:
8952
+ out["ledger"] = {
8953
+ "symbol": entry.get("symbol"),
8954
+ "reality": entry.get("reality"),
8955
+ "recommended": entry.get("recommended"),
8956
+ }
8957
+ return out
8958
+
8959
+
8902
8960
  def _safe_set_audio_properties(tl, p: Dict[str, Any]):
8903
8961
  item, err = _audio_item_from_params(tl, p)
8904
8962
  if err:
@@ -8938,7 +8996,18 @@ def _safe_set_audio_properties(tl, p: Dict[str, Any]):
8938
8996
  row["restore"] = False
8939
8997
  row["restore_error"] = str(exc)
8940
8998
  results[key] = row
8941
- return {"success": all(row.get("write") for row in results.values()), "results": results}
8999
+ success = all(row.get("write") for row in results.values())
9000
+ out = {"success": success, "results": results}
9001
+ # 'Volume' never writes and 'Pan' moves the video transform, not the audio
9002
+ # pan — so a bare {"write": false} (or a Pan that "succeeds" inertly) leaves
9003
+ # the caller guessing. Attach the ledger entry and the bake / preset route
9004
+ # whenever one of those keys was in play.
9005
+ limitation = _audio_write_limitation(
9006
+ {k: row.get("write") for k, row in results.items()}
9007
+ )
9008
+ if limitation:
9009
+ out["known_limitation"] = limitation
9010
+ return out
8942
9011
 
8943
9012
 
8944
9013
  def _voice_isolation_capabilities(tl, p: Dict[str, Any]):
@@ -26012,7 +26081,10 @@ def timeline(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str,
26012
26081
  audio_capabilities() -> {supported, partially_supported, unsupported}
26013
26082
  probe_audio_item(track_type?, track_index?, item_index?) -> {summary, audio_properties, source_audio_mapping}
26014
26083
  probe_audio_track(track_index?) -> {track_count, enabled, locked, sub_type, voice_isolation}
26015
- safe_set_audio_properties(properties, restore?, dry_run?, track_type?, track_index?, item_index?) -> {success, results}
26084
+ safe_set_audio_properties(properties, restore?, dry_run?, track_type?, track_index?, item_index?) -> {success, results, known_limitation?}
26085
+ — Volume/Pan/EQ writes are not honoured by Resolve's API; a request for
26086
+ any of them returns `known_limitation` with the bake / Fairlight-preset
26087
+ workaround. AudioSyncOffset writes do work.
26016
26088
  audio_mix_capability_report(...) -> {capabilities, mix_recommendations}
26017
26089
  voice_isolation_capabilities(track_index?, track_type?, item_index?) -> {timeline_track, item}
26018
26090
  audio_mapping_report(clip_ids?) -> {timeline_items, media_pool_items}
@@ -27091,7 +27163,10 @@ def timeline_item(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[
27091
27163
  get_composite(...) -> {Opacity, CompositeMode}
27092
27164
  set_composite(Opacity?, CompositeMode?, ...) -> {success}
27093
27165
  get_audio(...) -> {Volume, Pan, AudioSyncOffset, ...}
27094
- set_audio(Volume?, Pan?, ...) -> {success}
27166
+ set_audio(Volume?, Pan?, ...) -> {success, known_limitation?}
27167
+ — Resolve ignores Volume/Pan/EQ writes on audio; those return a
27168
+ `known_limitation` block (bake gain into the media, or apply a
27169
+ Fairlight preset). AudioSyncOffset / AudioSyncOffsetIsManual do work.
27095
27170
  get_keyframes(property, ...) -> {property, count, keyframes}
27096
27171
  add_keyframe(property, frame, value, ...) -> {success}
27097
27172
  modify_keyframe(property, frame, new_value?, new_frame?, ...) -> {success}
@@ -27326,7 +27401,15 @@ def timeline_item(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[
27326
27401
  for k, v in p.items():
27327
27402
  if k in valid:
27328
27403
  results[k] = bool(item.SetProperty(k, v))
27329
- return _ok(**results) if results else _err(f"Specify one or more of: {', '.join(sorted(valid))}")
27404
+ if not results:
27405
+ return _err(f"Specify one or more of: {', '.join(sorted(valid))}")
27406
+ out = _ok(**results)
27407
+ # Volume/Pan writes go nowhere on audio (see _audio_write_limitation);
27408
+ # AudioSyncOffset does work, so only flag when a level/pan key was asked.
27409
+ limitation = _audio_write_limitation(results)
27410
+ if limitation:
27411
+ out["known_limitation"] = limitation
27412
+ return out
27330
27413
 
27331
27414
  # ── Keyframes ──
27332
27415
  elif action == "get_keyframes":