davinci-resolve-mcp 2.97.0 → 2.97.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 +55 -0
- package/README.md +1 -1
- package/README.zh-CN.md +2 -2
- package/install.py +1 -1
- package/package.json +1 -1
- package/src/analysis_dashboard.py +60 -7
- package/src/granular/common.py +1 -1
- package/src/server.py +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,61 @@
|
|
|
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.97.2
|
|
6
|
+
|
|
7
|
+
**The panel's inventory snapshot survives a large project.** Follow-up to the
|
|
8
|
+
v2.97.1 fix: now that `inventory_limit` actually reaches the panel, the cache it
|
|
9
|
+
feeds has to cope with what arrives.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **The localStorage snapshot silently stopped working above ~5MB.** The panel
|
|
14
|
+
caches the last inventory so reopening it paints the previous view instantly
|
|
15
|
+
instead of "connection pending". That write was unbounded — and with
|
|
16
|
+
`inventory_limit` reaching 10000 clips, each carrying a file path, bin path,
|
|
17
|
+
status reasons, and the analysis overlay, it overruns the per-origin quota. The
|
|
18
|
+
write then threw, was caught, logged a `console.warn` no one reads, and the
|
|
19
|
+
instant-paint feature quietly did nothing on exactly the big projects where a
|
|
20
|
+
slow first fetch makes it worth having. The snapshot is now capped at 750
|
|
21
|
+
clips; on a quota failure the panel prunes cached inventories (including
|
|
22
|
+
snapshots for projects the user has moved on from) and retries once, and if
|
|
23
|
+
that still fails it removes its own key so a snapshot cannot outlive the walk
|
|
24
|
+
it described.
|
|
25
|
+
- **A truncated snapshot no longer reads as a complete inventory.** A capped
|
|
26
|
+
snapshot carries `snapshot_partial` and the true clip count, and both surfaces
|
|
27
|
+
that show a number say so — the header reads `(cached preview of 3015)` and the
|
|
28
|
+
poll status reads `cached first 750 of 3015`. Capping the cache without this
|
|
29
|
+
would have re-created the v2.97.1 bug one layer down: a number presented as the
|
|
30
|
+
inventory that isn't.
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- `tests/test_dashboard_inventory_snapshot.py` — guards that the inventory fetch
|
|
35
|
+
sends no `limit` of its own (the v2.97.1 regression), that the snapshot stays
|
|
36
|
+
capped, and that a partial snapshot declares itself.
|
|
37
|
+
|
|
38
|
+
## What's New in v2.97.1
|
|
39
|
+
|
|
40
|
+
**The control panel honors `media_analysis.inventory_limit` again.** Thanks to
|
|
41
|
+
[@albertfdp](https://github.com/albertfdp) for finding and fixing this
|
|
42
|
+
([#148](https://github.com/samuelgursky/davinci-resolve-mcp/pull/148)).
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
|
|
46
|
+
- **A configurable preference the panel could never reach.** `/api/resolve/media`
|
|
47
|
+
falls back to the `media_analysis.inventory_limit` preference (1–10000,
|
|
48
|
+
default 500) when the request carries no `limit`, and the Preferences pane has
|
|
49
|
+
always let you set it. But `refreshResolveMedia()` — the panel's only caller of
|
|
50
|
+
that endpoint, used for the first load, the manual refresh button, and the
|
|
51
|
+
background poll alike — hardcoded `?limit=500`, so the query param won every
|
|
52
|
+
time and the preference was inert. On a 3015-clip project the Overview panel
|
|
53
|
+
showed the first 500 clips and rendered real Media Pool bins as empty. The
|
|
54
|
+
fetch no longer sends `limit`; the server-side preference applies. Verified
|
|
55
|
+
against a live 3015-clip project: 3015 clips across 32 bins after the fix.
|
|
56
|
+
|
|
57
|
+
This is the silent-lie class again — a setting that round-trips through the
|
|
58
|
+
preferences store, reports itself as saved, and changes nothing observable.
|
|
59
|
+
|
|
5
60
|
## What's New in v2.97.0
|
|
6
61
|
|
|
7
62
|
**`timeline_frame(action="capture")` now renders the frame.** v2.96.0 shipped it
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
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
8
|
[-blue.svg)](#server-modes)
|
package/README.zh-CN.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
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
8
|
[-blue.svg)](#服务器模式)
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
[](https://www.python.org/downloads/)
|
|
13
13
|
[](https://opensource.org/licenses/MIT)
|
|
14
14
|
|
|
15
|
-
> 本翻译对应 v2.97.
|
|
15
|
+
> 本翻译对应 v2.97.2 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
|
|
16
16
|
|
|
17
17
|
一个 Model Context Protocol (MCP) 服务器,让 AI 助手通过官方脚本 API 控制 DaVinci Resolve Studio(达芬奇)。它提供完整的 API 覆盖,外加带护栏的工作流助手,涵盖剪辑、媒体池整理、渲染设置、审阅标记、调色、Fusion、Fairlight、项目生命周期任务、扩展开发,以及不碰源媒体的媒体分析。
|
|
18
18
|
|
package/install.py
CHANGED
|
@@ -37,7 +37,7 @@ from src.utils.update_check import (
|
|
|
37
37
|
|
|
38
38
|
# ─── Version ──────────────────────────────────────────────────────────────────
|
|
39
39
|
|
|
40
|
-
VERSION = "2.97.
|
|
40
|
+
VERSION = "2.97.2"
|
|
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
|
@@ -5981,7 +5981,10 @@ HTML = r"""<!doctype html>
|
|
|
5981
5981
|
const clips = filteredResolveClips(allClips);
|
|
5982
5982
|
const projectLabel = payload.project?.name || 'Current Resolve project';
|
|
5983
5983
|
const visibleCounts = summarizeVisibleClips(clips);
|
|
5984
|
-
|
|
5984
|
+
// A cached snapshot may hold only the first INVENTORY_SNAPSHOT_MAX_CLIPS
|
|
5985
|
+
// clips; say so rather than letting the prefix read as the clip count.
|
|
5986
|
+
const partialNote = payload.snapshot_partial ? ` (cached preview of ${payload.snapshot_total})` : '';
|
|
5987
|
+
project.textContent = `${projectLabel} · ${clipLabel(clips.length)}${partialNote}`;
|
|
5985
5988
|
const hasAnalyzableClips = clips.some(clip => clip.analyzable);
|
|
5986
5989
|
selectBtn.disabled = !hasAnalyzableClips;
|
|
5987
5990
|
analyzeBtn.disabled = !hasAnalyzableClips;
|
|
@@ -6621,10 +6624,12 @@ HTML = r"""<!doctype html>
|
|
|
6621
6624
|
// probe (the server re-applies only the local analysis overlay); manual /
|
|
6622
6625
|
// first loads do a full Media Pool walk with a fresh probe. The ETag lets
|
|
6623
6626
|
// an unchanged poll short-circuit to 304 and skip the table re-render.
|
|
6624
|
-
|
|
6627
|
+
// No `limit` param: the server applies the media_analysis.inventory_limit
|
|
6628
|
+
// preference, and sending one here would override what the user configured.
|
|
6629
|
+
const query = options.silent ? '?probe=0&reuse=1' : '';
|
|
6625
6630
|
const headers = {};
|
|
6626
6631
|
if (state.mediaETag) headers['If-None-Match'] = state.mediaETag;
|
|
6627
|
-
const res = await fetch(`/api/resolve/media
|
|
6632
|
+
const res = await fetch(`/api/resolve/media${query}`, { headers, cache: 'no-store' });
|
|
6628
6633
|
state.mediaLastRefresh = new Date();
|
|
6629
6634
|
state.resolveMediaStale = false;
|
|
6630
6635
|
if (res.status === 304) return;
|
|
@@ -6645,16 +6650,60 @@ HTML = r"""<!doctype html>
|
|
|
6645
6650
|
// Persist the last good inventory so reopening the dashboard paints the
|
|
6646
6651
|
// previous snapshot instantly (with a "refreshing" hint) instead of sitting
|
|
6647
6652
|
// on "connection pending" until the first fetch returns.
|
|
6653
|
+
// localStorage gives us roughly 5MB per origin, and a clip record — file
|
|
6654
|
+
// path, bin path, status reasons, analysis overlay — runs several hundred
|
|
6655
|
+
// bytes. inventory_limit reaches 10000, so a large project overruns the
|
|
6656
|
+
// quota, and the write then throws and leaves the panel with no snapshot at
|
|
6657
|
+
// all. Cache a bounded prefix instead, and carry the real clip count so a
|
|
6658
|
+
// partial snapshot never presents itself as the whole inventory.
|
|
6659
|
+
const INVENTORY_SNAPSHOT_MAX_CLIPS = 750;
|
|
6660
|
+
|
|
6648
6661
|
function inventorySnapshotKey() {
|
|
6649
6662
|
return 'resolveMcpInventory:' + (state.activeContext?.project_root || state.boot?.project_root || state.boot?.project_name || 'default');
|
|
6650
6663
|
}
|
|
6664
|
+
function inventorySnapshotPayload(payload) {
|
|
6665
|
+
const clips = payload.clips || [];
|
|
6666
|
+
if (clips.length <= INVENTORY_SNAPSHOT_MAX_CLIPS) return payload;
|
|
6667
|
+
return {
|
|
6668
|
+
...payload,
|
|
6669
|
+
clips: clips.slice(0, INVENTORY_SNAPSHOT_MAX_CLIPS),
|
|
6670
|
+
snapshot_partial: true,
|
|
6671
|
+
snapshot_total: clips.length,
|
|
6672
|
+
};
|
|
6673
|
+
}
|
|
6674
|
+
// Drop every cached inventory, including this project's own stale value —
|
|
6675
|
+
// the panel is about to rewrite it, and snapshots for projects the user has
|
|
6676
|
+
// since moved on from are the usual reason the quota is full.
|
|
6677
|
+
function pruneInventorySnapshots() {
|
|
6678
|
+
try {
|
|
6679
|
+
const keys = [];
|
|
6680
|
+
for (let i = 0; i < localStorage.length; i += 1) {
|
|
6681
|
+
const key = localStorage.key(i);
|
|
6682
|
+
if (key && key.startsWith('resolveMcpInventory:')) keys.push(key);
|
|
6683
|
+
}
|
|
6684
|
+
keys.forEach(key => localStorage.removeItem(key));
|
|
6685
|
+
} catch (error) {
|
|
6686
|
+
console.warn('Could not prune inventory snapshots', error);
|
|
6687
|
+
}
|
|
6688
|
+
}
|
|
6651
6689
|
function saveInventorySnapshot(payload) {
|
|
6652
6690
|
if (!payload?.resolve_available) return;
|
|
6691
|
+
const key = inventorySnapshotKey();
|
|
6692
|
+
const raw = JSON.stringify({ saved_at: Date.now(), payload: inventorySnapshotPayload(payload) });
|
|
6653
6693
|
try {
|
|
6654
|
-
localStorage.setItem(
|
|
6694
|
+
localStorage.setItem(key, raw);
|
|
6655
6695
|
} catch (error) {
|
|
6656
|
-
//
|
|
6657
|
-
|
|
6696
|
+
// Out of quota: clear the cached inventories and try once more.
|
|
6697
|
+
pruneInventorySnapshots();
|
|
6698
|
+
try {
|
|
6699
|
+
localStorage.setItem(key, raw);
|
|
6700
|
+
} catch (retryError) {
|
|
6701
|
+
// Still no room — drop our key so a snapshot from an older walk can't
|
|
6702
|
+
// outlive the inventory it described. Non-fatal; the panel just paints
|
|
6703
|
+
// "connection pending" until the first fetch returns.
|
|
6704
|
+
try { localStorage.removeItem(key); } catch (removeError) { /* nothing left to do */ }
|
|
6705
|
+
console.warn('Could not cache inventory snapshot', retryError);
|
|
6706
|
+
}
|
|
6658
6707
|
}
|
|
6659
6708
|
}
|
|
6660
6709
|
function loadInventorySnapshot() {
|
|
@@ -7168,7 +7217,11 @@ HTML = r"""<!doctype html>
|
|
|
7168
7217
|
const poll = enabled ? `polling every ${Math.round(interval / 1000)}s` : 'polling off';
|
|
7169
7218
|
const visible = state.resolveMedia?.clips ? clipLabel(filteredResolveClips(state.resolveMedia.clips).length) : 'no media snapshot';
|
|
7170
7219
|
const prefix = state.mediaRefreshing ? 'refreshing' : poll;
|
|
7171
|
-
const stale = state.resolveMediaStale
|
|
7220
|
+
const stale = state.resolveMediaStale
|
|
7221
|
+
? (state.resolveMedia?.snapshot_partial
|
|
7222
|
+
? `cached first ${state.resolveMedia.clips.length} of ${state.resolveMedia.snapshot_total} · `
|
|
7223
|
+
: 'cached · ')
|
|
7224
|
+
: '';
|
|
7172
7225
|
el.textContent = `${stale}${prefix} · last ${last} · ${visible}${extra ? ` · ${extra}` : ''}`;
|
|
7173
7226
|
}
|
|
7174
7227
|
|
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.97.
|
|
90
|
+
VERSION = "2.97.2"
|
|
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()}")
|