davinci-resolve-mcp 3.2.1 → 3.2.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 +47 -0
- package/README.md +1 -1
- package/README.zh-CN.md +2 -2
- package/docs/process/release-process.md +8 -1
- package/install.py +1 -1
- package/package.json +1 -1
- package/src/granular/common.py +1 -1
- package/src/server.py +1 -1
- package/src/utils/media_analysis.py +53 -3
- package/src/utils/timeline_brain_db.py +42 -9
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,53 @@
|
|
|
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 v3.2.2 — an analysis root that is deleted is actually let go of
|
|
6
|
+
|
|
7
|
+
Contributed by @Dev-next-gen (#228), generalised to the second site and to the
|
|
8
|
+
connection cache underneath both.
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **`cleanup_artifacts(frames_only=false)` reported success whether or not it
|
|
13
|
+
removed anything.** The analysis root contains
|
|
14
|
+
`_soul/timeline_brain.sqlite`, which `timeline_brain_db` keeps open in a
|
|
15
|
+
process-wide cache for the life of the server. Nothing let go of it before
|
|
16
|
+
the `shutil.rmtree`, and the rmtree runs with `ignore_errors=True`, so both
|
|
17
|
+
failure modes were swallowed. On Windows the open handle makes the DB
|
|
18
|
+
undeletable: the root survives with `_soul/` and the brain-edit history still
|
|
19
|
+
in it while the tool returns `{"success": true}`. On POSIX the root is
|
|
20
|
+
removed but the stale connection stays cached, so the next write for that
|
|
21
|
+
project goes to a file with no directory entry and is lost — the dashboard,
|
|
22
|
+
which opens the path fresh, sees nothing. `timeline_brain_db.close()` now
|
|
23
|
+
releases one project's connection, and the cleanup returns `success: false`
|
|
24
|
+
if the root is still on disk afterwards. (#228)
|
|
25
|
+
- **The same bug at a second site.** A `session_only` run without
|
|
26
|
+
`keep_artifacts` ingests every report into the brain DB under its output root
|
|
27
|
+
and then deletes that root, with the connection still cached. Because each
|
|
28
|
+
such run gets a fresh temp root, the cache accumulated one dead connection
|
|
29
|
+
per run. Both sites now go through one helper, and
|
|
30
|
+
`artifacts_cleaned_up` reports whether the removal happened rather than that
|
|
31
|
+
it was attempted.
|
|
32
|
+
- **`close()` released nothing when the root was spelled differently.** The
|
|
33
|
+
connection cache keyed on the caller's spelling of the path, and callers
|
|
34
|
+
disagree by construction: `media_analysis` realpaths a root before using it,
|
|
35
|
+
while its own callers pass what the user typed. On macOS that alone was
|
|
36
|
+
enough — every temp root under `/var/folders` is a symlink to
|
|
37
|
+
`/private/var/folders` — so `close()` computed a key that was never in the
|
|
38
|
+
cache, popped nothing, and the fix above silently did not apply. Two
|
|
39
|
+
spellings of one root also opened two connections to one SQLite file. The
|
|
40
|
+
cache now keys on the resolved DB path.
|
|
41
|
+
|
|
42
|
+
### Release process
|
|
43
|
+
|
|
44
|
+
- **`tests.test_release_surface_drift` is now in the documented gate list.** It
|
|
45
|
+
asserts the README badge, the `README.zh-CN.md` badge and translation line,
|
|
46
|
+
and a `CHANGELOG.md` entry all match `package.json` — and it was the one
|
|
47
|
+
version-surface check missing from `docs/process/release-process.md`. v3.2.1
|
|
48
|
+
shipped with a zh-CN badge still reading v3.2.0 because every documented gate
|
|
49
|
+
passed while none of them looks at a version surface. That badge is corrected
|
|
50
|
+
here.
|
|
51
|
+
|
|
5
52
|
## What's New in v3.2.1 — three correctness fixes to the LUT tool
|
|
6
53
|
|
|
7
54
|
Contributed by @Dev-next-gen (#225, #226, #227), each found by reading the v3.2.0
|
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
|
-
> 本翻译对应 v3.2.
|
|
15
|
+
> 本翻译对应 v3.2.2 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
|
|
16
16
|
|
|
17
17
|
一个 Model Context Protocol (MCP) 服务器,让 AI 助手通过官方脚本 API 控制 DaVinci Resolve Studio(达芬奇)。它提供完整的 API 覆盖,外加带护栏的工作流助手,涵盖剪辑、媒体池整理、渲染设置、审阅标记、调色、Fusion、Fairlight、项目生命周期任务、扩展开发,以及不碰源媒体的媒体分析。
|
|
18
18
|
|
|
@@ -75,7 +75,7 @@ venv/bin/python scripts/audit_api_parity.py
|
|
|
75
75
|
venv/bin/python scripts/gen_api_limitations.py --check
|
|
76
76
|
venv/bin/python scripts/audit_readwrite_symmetry.py --check
|
|
77
77
|
node scripts/agent-rules/generate.mjs --check
|
|
78
|
-
venv/bin/python -m unittest tests.test_static_undefined_names tests.test_duplicate_definitions tests.test_action_list_drift tests.test_panel_docs_drift tests.test_doc_tool_counts tests.test_agent_rules_drift
|
|
78
|
+
venv/bin/python -m unittest tests.test_static_undefined_names tests.test_duplicate_definitions tests.test_action_list_drift tests.test_panel_docs_drift tests.test_doc_tool_counts tests.test_agent_rules_drift tests.test_release_surface_drift
|
|
79
79
|
node bin/davinci-resolve-mcp.mjs --help
|
|
80
80
|
node bin/davinci-resolve-mcp.mjs --version
|
|
81
81
|
npm pack --dry-run
|
|
@@ -89,6 +89,13 @@ regeneration is in the working tree when the test reads it — that ordering is
|
|
|
89
89
|
the check is a regeneration followed by a test, not a `git diff --exit-code`,
|
|
90
90
|
which would fire on the release bump's own legitimate change.
|
|
91
91
|
|
|
92
|
+
`test_release_surface_drift` is the gate on the version bump itself: it asserts
|
|
93
|
+
the README badge, the `README.zh-CN.md` badge and its `本翻译对应 vX.Y.Z` line, and
|
|
94
|
+
a `CHANGELOG.md` entry all match `package.json`. It was not in this list until
|
|
95
|
+
v3.2.2, and v3.2.1 shipped with a zh-CN badge still reading v3.2.0 as a direct
|
|
96
|
+
result — every other gate passed, because none of them looks at a version
|
|
97
|
+
surface. Run it before tagging, not after.
|
|
98
|
+
|
|
92
99
|
`test_duplicate_definitions` asserts no module-level name is defined twice under
|
|
93
100
|
`src/`. A second `def foo` silently replaces the first, and in a module the size
|
|
94
101
|
of `src/server.py` the two can be thousands of lines apart with different
|
package/install.py
CHANGED
|
@@ -37,7 +37,7 @@ from src.utils.update_check import (
|
|
|
37
37
|
|
|
38
38
|
# ─── Version ──────────────────────────────────────────────────────────────────
|
|
39
39
|
|
|
40
|
-
VERSION = "3.2.
|
|
40
|
+
VERSION = "3.2.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
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 = "3.2.
|
|
90
|
+
VERSION = "3.2.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()}")
|
package/src/server.py
CHANGED
|
@@ -2590,6 +2590,48 @@ def _read_json(path: str) -> Dict[str, Any]:
|
|
|
2590
2590
|
return json.load(f)
|
|
2591
2591
|
|
|
2592
2592
|
|
|
2593
|
+
def _drop_brain_db_and_rmtree(project_root: str, cleanup_root: str) -> bool:
|
|
2594
|
+
"""Delete `cleanup_root`, releasing `project_root`'s brain DB first.
|
|
2595
|
+
|
|
2596
|
+
`<project_root>/_soul/timeline_brain.sqlite` is held open in a
|
|
2597
|
+
process-wide cache for the life of the server, and nothing else lets go of
|
|
2598
|
+
it. Deleting the root underneath that handle fails differently on each
|
|
2599
|
+
platform and silently on both, because rmtree runs with
|
|
2600
|
+
``ignore_errors=True``:
|
|
2601
|
+
|
|
2602
|
+
- Windows refuses to delete the open file, so the root survives with
|
|
2603
|
+
`_soul/` and the brain-edit history still in it while the caller is told
|
|
2604
|
+
it is gone.
|
|
2605
|
+
- POSIX unlinks it, but the cache then hands the next writer a connection
|
|
2606
|
+
to a file with no directory entry, so the write lands nowhere.
|
|
2607
|
+
|
|
2608
|
+
Returns whether `cleanup_root` is actually gone afterwards, so callers can
|
|
2609
|
+
report a removal that happened rather than one they attempted.
|
|
2610
|
+
"""
|
|
2611
|
+
from src.utils import timeline_brain_db as _brain_db
|
|
2612
|
+
|
|
2613
|
+
_brain_db.close(project_root)
|
|
2614
|
+
shutil.rmtree(cleanup_root, ignore_errors=True)
|
|
2615
|
+
return not os.path.isdir(cleanup_root)
|
|
2616
|
+
|
|
2617
|
+
|
|
2618
|
+
def _release_session_root(manifest: Dict[str, Any], project_root: str,
|
|
2619
|
+
cleanup_root: str) -> bool:
|
|
2620
|
+
"""Session-only artifact cleanup: same rule as `cleanup_artifacts`.
|
|
2621
|
+
|
|
2622
|
+
A session-only run ingests every report into the brain DB under
|
|
2623
|
+
`project_root` and then throws the root away, and each run gets a fresh
|
|
2624
|
+
temp root -- so without the release the cache accumulates one dead
|
|
2625
|
+
connection per run.
|
|
2626
|
+
"""
|
|
2627
|
+
removed = _drop_brain_db_and_rmtree(project_root, cleanup_root)
|
|
2628
|
+
if not removed:
|
|
2629
|
+
manifest.setdefault("memory_layer_warnings", []).append(
|
|
2630
|
+
f"Could not fully remove the session analysis root: {cleanup_root}"
|
|
2631
|
+
)
|
|
2632
|
+
return removed
|
|
2633
|
+
|
|
2634
|
+
|
|
2593
2635
|
def _ingest_report_into_db(project_root: str, report: Dict[str, Any], clip_dir: Optional[str]) -> Dict[str, Any]:
|
|
2594
2636
|
"""C1 — write a report into the DB-canonical store (rows in a transaction).
|
|
2595
2637
|
|
|
@@ -5971,8 +6013,9 @@ async def execute_plan_async(
|
|
|
5971
6013
|
and _is_relative_to(output_root, candidate)
|
|
5972
6014
|
):
|
|
5973
6015
|
cleanup_root = candidate
|
|
5974
|
-
|
|
5975
|
-
|
|
6016
|
+
manifest["artifacts_cleaned_up"] = _release_session_root(
|
|
6017
|
+
manifest, output_root, cleanup_root
|
|
6018
|
+
)
|
|
5976
6019
|
manifest["artifact_cleanup_root"] = cleanup_root
|
|
5977
6020
|
|
|
5978
6021
|
return manifest
|
|
@@ -7057,7 +7100,14 @@ def cleanup_artifacts(project_root: str, *, frames_only: bool = True) -> Dict[st
|
|
|
7057
7100
|
shutil.rmtree(full, ignore_errors=True)
|
|
7058
7101
|
removed.append(full)
|
|
7059
7102
|
else:
|
|
7060
|
-
|
|
7103
|
+
# The whole root goes, including `_soul/timeline_brain.sqlite`.
|
|
7104
|
+
if not _drop_brain_db_and_rmtree(root, root):
|
|
7105
|
+
return {
|
|
7106
|
+
"success": False,
|
|
7107
|
+
"error": f"Could not fully remove the project analysis root: {root}",
|
|
7108
|
+
"removed": removed,
|
|
7109
|
+
"frames_only": frames_only,
|
|
7110
|
+
}
|
|
7061
7111
|
removed.append(root)
|
|
7062
7112
|
return {"success": True, "removed": removed, "frames_only": frames_only}
|
|
7063
7113
|
|
|
@@ -50,6 +50,26 @@ def db_path_for_project(project_root: str) -> str:
|
|
|
50
50
|
return os.path.join(project_root, SOUL_DIRNAME, DB_FILENAME)
|
|
51
51
|
|
|
52
52
|
|
|
53
|
+
def _cache_key(project_root: str) -> str:
|
|
54
|
+
"""The connection-cache key for a project root: its DB path, realpath'd.
|
|
55
|
+
|
|
56
|
+
Two spellings of one root must not become two cached connections to one
|
|
57
|
+
file. They do without this, because callers disagree about spelling by
|
|
58
|
+
construction -- `media_analysis` normalizes a root through `realpath`
|
|
59
|
+
before using it, while its own callers pass whatever the user typed. On
|
|
60
|
+
macOS that alone is enough: every temp root under `/var/folders/...` is a
|
|
61
|
+
symlink to `/private/var/folders/...`.
|
|
62
|
+
|
|
63
|
+
The damage is not just a duplicate. `close()` pops by key, so a mismatch
|
|
64
|
+
makes it silently no-op and leave the connection it was called to release
|
|
65
|
+
-- which is the whole point of calling it before deleting the root.
|
|
66
|
+
|
|
67
|
+
`realpath` on a path that does not exist yet resolves the ancestors that do
|
|
68
|
+
and leaves the rest literal, which is what a not-yet-created DB needs.
|
|
69
|
+
"""
|
|
70
|
+
return os.path.realpath(db_path_for_project(project_root))
|
|
71
|
+
|
|
72
|
+
|
|
53
73
|
def _ensure_parent_dir(path: str) -> None:
|
|
54
74
|
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
55
75
|
|
|
@@ -224,7 +244,7 @@ def connect(project_root: str) -> sqlite3.Connection:
|
|
|
224
244
|
"""
|
|
225
245
|
if not project_root:
|
|
226
246
|
raise ValueError("project_root is required")
|
|
227
|
-
path =
|
|
247
|
+
path = _cache_key(project_root)
|
|
228
248
|
with _CONNECTION_LOCK:
|
|
229
249
|
existing = _CONNECTIONS.get(path)
|
|
230
250
|
if existing is not None:
|
|
@@ -250,6 +270,25 @@ def close_all() -> None:
|
|
|
250
270
|
_CONNECTIONS.clear()
|
|
251
271
|
|
|
252
272
|
|
|
273
|
+
def close(project_root: str) -> None:
|
|
274
|
+
"""Drop and close the cached connection for `project_root`, if any.
|
|
275
|
+
|
|
276
|
+
Callers that are about to delete or move a project's analysis root need
|
|
277
|
+
this. On Windows the open handle makes `_soul/timeline_brain.sqlite`
|
|
278
|
+
undeletable; on POSIX the cache would otherwise hand the next writer a
|
|
279
|
+
connection to a file that no longer has a directory entry, so the write
|
|
280
|
+
lands nowhere.
|
|
281
|
+
"""
|
|
282
|
+
path = _cache_key(project_root)
|
|
283
|
+
with _CONNECTION_LOCK:
|
|
284
|
+
conn = _CONNECTIONS.pop(path, None)
|
|
285
|
+
if conn is not None:
|
|
286
|
+
try:
|
|
287
|
+
conn.close()
|
|
288
|
+
except sqlite3.Error:
|
|
289
|
+
pass
|
|
290
|
+
|
|
291
|
+
|
|
253
292
|
@contextmanager
|
|
254
293
|
def transaction(project_root: str) -> Iterator[sqlite3.Connection]:
|
|
255
294
|
"""Context manager wrapping a write transaction.
|
|
@@ -291,14 +330,8 @@ def transaction(project_root: str) -> Iterator[sqlite3.Connection]:
|
|
|
291
330
|
|
|
292
331
|
def reset_for_test(project_root: str) -> None:
|
|
293
332
|
"""Drop + recreate every table. Tests only."""
|
|
294
|
-
path =
|
|
295
|
-
|
|
296
|
-
conn = _CONNECTIONS.pop(path, None)
|
|
297
|
-
if conn is not None:
|
|
298
|
-
try:
|
|
299
|
-
conn.close()
|
|
300
|
-
except sqlite3.Error:
|
|
301
|
-
pass
|
|
333
|
+
path = _cache_key(project_root)
|
|
334
|
+
close(project_root)
|
|
302
335
|
for suffix in ("", "-wal", "-shm"):
|
|
303
336
|
try:
|
|
304
337
|
os.remove(path + suffix)
|