davinci-resolve-mcp 2.42.0 → 2.44.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 +53 -0
- package/README.md +1 -1
- package/docs/SKILL.md +38 -2
- package/docs/guides/control-panel.md +8 -1
- package/install.py +1 -1
- package/package.json +1 -1
- package/src/analysis_dashboard.py +128 -1
- package/src/granular/common.py +1 -1
- package/src/server.py +77 -1
- package/src/utils/embeddings.py +668 -0
- package/src/utils/entities.py +579 -0
- package/src/utils/media_analysis.py +44 -0
- package/src/utils/timeline_brain_db.py +80 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,59 @@
|
|
|
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.44.0
|
|
6
|
+
|
|
7
|
+
Cross-clip entities + bin briefing v2 — Phase D of the analysis +
|
|
8
|
+
edit-engine program. Recurring people/places/props found by clustering the
|
|
9
|
+
visual embeddings, confirmed with one vision call per cluster.
|
|
10
|
+
|
|
11
|
+
- **Added** schema v11 (`entities` + `entity_appearances`) and
|
|
12
|
+
`src/utils/entities.py`: union-find clustering over the v10 CLIP frame
|
|
13
|
+
vectors (cosine threshold, no new deps), representative-frame selection,
|
|
14
|
+
ghost pruning across re-runs (labeled entities persist), and a
|
|
15
|
+
detection-state stash so `entity_index` always resolves against the exact
|
|
16
|
+
ordering the payload was issued with.
|
|
17
|
+
- **Added** `media_analysis` actions: `detect_entities` (clusters + deferred
|
|
18
|
+
one-frame-per-cluster confirmation payload, caps pre-checked),
|
|
19
|
+
`commit_entities` (kind/label/description with conservative-label rules;
|
|
20
|
+
`merge_with` collapses duplicate clusters), `list_entities`,
|
|
21
|
+
`prepare_bin_briefing` (entities + per-clip summaries, text-only), and
|
|
22
|
+
`commit_bin_summary` (host-synthesized briefing written above the v2.0
|
|
23
|
+
aggregate in `memory/bin_summary.md`).
|
|
24
|
+
- **Added** a "Recurring across this bin" card on the panel's Review page
|
|
25
|
+
(`/api/entities`), shown once labeled entities exist.
|
|
26
|
+
- **Validation**: full offline suite (1105 tests; 10 new). Live on the real
|
|
27
|
+
sample root: 3 clusters detected from 16 CLIP vectors; host-chat
|
|
28
|
+
confirmation labeled the shattered-windshield POV and the white rental
|
|
29
|
+
sedan and merged the sedan's two clusters; bin briefing synthesized and
|
|
30
|
+
committed; panel card verified and screenshots regenerated.
|
|
31
|
+
|
|
32
|
+
## What's New in v2.43.0
|
|
33
|
+
|
|
34
|
+
Embeddings + similarity search — Phase C of the analysis + edit-engine
|
|
35
|
+
program. "Find clips/shots like this," locally, with no vendor token cost.
|
|
36
|
+
|
|
37
|
+
- **Added** `src/utils/embeddings.py` and schema v10 (`embeddings` table:
|
|
38
|
+
one float32 vector per entity/kind/model, with content hashes so re-runs
|
|
39
|
+
only re-embed what changed). Brute-force cosine — numpy when present.
|
|
40
|
+
- **Added** `media_analysis` actions `build_embeddings` (idempotent; clip
|
|
41
|
+
summaries, shot descriptions + deep field groups, transcript segments,
|
|
42
|
+
sampled frames with per-shot mean vectors) and `find_similar` (query by
|
|
43
|
+
free text, clip, or shot; `kind="text"|"visual"`; free-text visual queries
|
|
44
|
+
go through the CLIP text encoder).
|
|
45
|
+
- **Added** backend detection, never installation (the whisper pattern):
|
|
46
|
+
text = ollama `nomic-embed-text` (serving probe) or sentence-transformers;
|
|
47
|
+
visual = open_clip ViT-B-32. Capabilities and the Diagnostics → Tools page
|
|
48
|
+
list availability with install guidance.
|
|
49
|
+
- **Added** a `Semantic` toggle on the panel's Review search (shown only
|
|
50
|
+
when a text backend is detected) backed by `/api/search/semantic`, which
|
|
51
|
+
returns rows in the existing search-card shape.
|
|
52
|
+
- **Validation**: full offline suite (1095 tests; 14 new, backends mocked).
|
|
53
|
+
Live on the 2026-05-17 sample root: 54 text vectors in 1.2s via ollama
|
|
54
|
+
with correct top hits for three editorial queries; 27 CLIP vectors with
|
|
55
|
+
"cracked broken windshield glass" ranking the shattered-windshield frame
|
|
56
|
+
first; panel endpoint verified and screenshots regenerated.
|
|
57
|
+
|
|
5
58
|
## What's New in v2.42.0
|
|
6
59
|
|
|
7
60
|
Deep shot-level vision tier — Phase B of the analysis + edit-engine program.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# DaVinci Resolve MCP Server
|
|
2
2
|
|
|
3
|
-
[](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
|
|
4
4
|
[](https://www.npmjs.com/package/davinci-resolve-mcp)
|
|
5
5
|
[](docs/reference/api-coverage.md)
|
|
6
6
|
[-blue.svg)](#server-modes)
|
package/docs/SKILL.md
CHANGED
|
@@ -495,8 +495,44 @@ Key actions: `capabilities`, `install_guidance`, `resolve_output_root`, `plan`,
|
|
|
495
495
|
`cleanup_artifacts`, `db_status`, `db_ingest`, `get_panel_state`,
|
|
496
496
|
`set_panel_state`, `session_start_context`, `update_clip_field`,
|
|
497
497
|
`update_shot_field`, `get_field_history`, `revert_field`,
|
|
498
|
-
`list_corrections`, `deepen`, `commit_shot_vision`,
|
|
499
|
-
`
|
|
498
|
+
`list_corrections`, `deepen`, `commit_shot_vision`, `vision_pending_sweep`,
|
|
499
|
+
`build_embeddings`, `find_similar`, `detect_entities`, `commit_entities`,
|
|
500
|
+
`list_entities`, `prepare_bin_briefing`, and `commit_bin_summary`.
|
|
501
|
+
|
|
502
|
+
**Cross-clip entities + bin briefing v2 (v2.44.0+).** Recurring
|
|
503
|
+
people/places/props across a project's media, found cheaply and confirmed
|
|
504
|
+
with ONE vision call per cluster:
|
|
505
|
+
- `detect_entities(threshold?, min_cluster_size?)` clusters the v10 CLIP
|
|
506
|
+
frame vectors (build visual embeddings first), writes provisional entity
|
|
507
|
+
rows + appearances, and returns a deferred payload with one
|
|
508
|
+
representative frame per cluster (caps pre-checked, estimate inlined).
|
|
509
|
+
The host chat reads those frames and calls
|
|
510
|
+
`commit_entities(entities=[{entity_index, kind, label, description,
|
|
511
|
+
confidence, merge_with?}], vision_token)` — conservative labels only
|
|
512
|
+
(describe what's visible; never guess names). `merge_with` collapses
|
|
513
|
+
clusters that show the same entity.
|
|
514
|
+
- `list_entities` returns labeled entities with per-clip/shot appearances;
|
|
515
|
+
the panel's Review page shows a "Recurring across this bin" card.
|
|
516
|
+
- `prepare_bin_briefing` returns entities + per-clip summaries (text-only,
|
|
517
|
+
no vision cost); the host writes a colleague-style markdown briefing and
|
|
518
|
+
calls `commit_bin_summary(briefing, briefing_token)`, which lands in
|
|
519
|
+
`memory/bin_summary.md` above the v2.0 aggregate.
|
|
520
|
+
|
|
521
|
+
**Embeddings + similarity (v2.43.0+).** Local-compute semantic search; no
|
|
522
|
+
vendor tokens, so nothing here touches the caps ledger. Backends are
|
|
523
|
+
detected, never installed (capabilities lists them with install guidance):
|
|
524
|
+
text = ollama serving `nomic-embed-text` or sentence-transformers; visual =
|
|
525
|
+
open_clip (ViT-B-32, needs torch).
|
|
526
|
+
- `build_embeddings(kinds=["text","visual"]?, clip_id?)` — idempotent;
|
|
527
|
+
embeds clip summaries, shot descriptions (+ deep field groups), transcript
|
|
528
|
+
segments, and sampled frames (per-shot visual vector = mean of its
|
|
529
|
+
frames'). Only re-embeds entities whose content changed.
|
|
530
|
+
- `find_similar(text=… | clip_id=… | clip_id+shot_index, kind="text"|"visual",
|
|
531
|
+
entity_types?, limit?)` — brute-force cosine over the project's vectors.
|
|
532
|
+
Free-text visual queries use the CLIP text encoder ("cracked windshield"
|
|
533
|
+
finds the frame). Results carry scores plus clip/shot/segment context.
|
|
534
|
+
The panel search box gains a `Semantic` toggle when a text backend is
|
|
535
|
+
detected. Vectors live in the per-project DB (schema v10).
|
|
500
536
|
|
|
501
537
|
**Deep shot-level vision tier (v2.42.0+).** Opt-in, estimate-first. Two
|
|
502
538
|
entry points share one per-shot schema (Visual / Content / Production /
|
|
@@ -78,7 +78,14 @@ evidence base (analyzed / superseded / vision-pending / warnings) with
|
|
|
78
78
|
humanized source-trust and analysis-layer chips. Cards show a representative
|
|
79
79
|
thumbnail, duration, shot count, status, and summary one-liner; grid and list
|
|
80
80
|
layouts are available, and full-text search covers clips, summaries, tags, and
|
|
81
|
-
transcripts once the search index is built.
|
|
81
|
+
transcripts once the search index is built. When a local text-embedding
|
|
82
|
+
backend is detected (ollama with `nomic-embed-text`, or
|
|
83
|
+
sentence-transformers), a `Semantic` toggle appears next to the search box —
|
|
84
|
+
it searches by meaning instead of exact words, ranking clips, shots, and
|
|
85
|
+
transcript lines by similarity to your query. Once cross-clip entity
|
|
86
|
+
detection has run (`detect_entities` + a one-frame-per-cluster confirmation
|
|
87
|
+
in chat), a `Recurring across this bin` card lists the labeled people,
|
|
88
|
+
places, and objects with their shot counts.
|
|
82
89
|
|
|
83
90
|
### Clip detail
|
|
84
91
|
|
package/install.py
CHANGED
|
@@ -35,7 +35,7 @@ from src.utils.update_check import (
|
|
|
35
35
|
|
|
36
36
|
# ─── Version ──────────────────────────────────────────────────────────────────
|
|
37
37
|
|
|
38
|
-
VERSION = "2.
|
|
38
|
+
VERSION = "2.44.0"
|
|
39
39
|
# Only hard floor: mcp[cli] requires Python 3.10+. There is no upper bound —
|
|
40
40
|
# Resolve's scripting bridge loads into newer interpreters on recent builds
|
|
41
41
|
# (Python 3.14 verified against Resolve Studio 20.3.2). Older Resolve builds
|
package/package.json
CHANGED
|
@@ -2114,6 +2114,30 @@ HTML = r"""<!doctype html>
|
|
|
2114
2114
|
margin-bottom: var(--space-3);
|
|
2115
2115
|
flex-wrap: wrap;
|
|
2116
2116
|
}
|
|
2117
|
+
.review-semantic-toggle {
|
|
2118
|
+
display: inline-flex;
|
|
2119
|
+
gap: 6px;
|
|
2120
|
+
align-items: center;
|
|
2121
|
+
color: var(--text-secondary);
|
|
2122
|
+
font-size: var(--ops-text-label);
|
|
2123
|
+
white-space: nowrap;
|
|
2124
|
+
}
|
|
2125
|
+
.review-entities {
|
|
2126
|
+
margin-bottom: var(--space-3);
|
|
2127
|
+
padding: var(--space-2) var(--space-3);
|
|
2128
|
+
border: 1px solid var(--border-default);
|
|
2129
|
+
border-radius: var(--radius-md);
|
|
2130
|
+
}
|
|
2131
|
+
.review-entities-title {
|
|
2132
|
+
color: var(--text-secondary);
|
|
2133
|
+
font-size: var(--ops-text-label);
|
|
2134
|
+
margin-bottom: 6px;
|
|
2135
|
+
}
|
|
2136
|
+
.review-entities-chips {
|
|
2137
|
+
display: flex;
|
|
2138
|
+
gap: var(--space-2);
|
|
2139
|
+
flex-wrap: wrap;
|
|
2140
|
+
}
|
|
2117
2141
|
.review-bin-filters input[type="search"] {
|
|
2118
2142
|
flex: 1 1 280px;
|
|
2119
2143
|
min-width: 200px;
|
|
@@ -4065,6 +4089,9 @@ HTML = r"""<!doctype html>
|
|
|
4065
4089
|
</div>
|
|
4066
4090
|
<div class="review-bin-filters">
|
|
4067
4091
|
<input id="reviewSearchInput" type="search" placeholder="Search clips, summaries, tags, transcripts…" autocomplete="off">
|
|
4092
|
+
<label id="reviewSemanticToggle" class="review-semantic-toggle" style="display:none" title="Search by meaning using local text embeddings instead of exact words.">
|
|
4093
|
+
<input id="reviewSemanticCheckbox" type="checkbox"> Semantic
|
|
4094
|
+
</label>
|
|
4068
4095
|
<select id="reviewBinFilter" aria-label="Filter by bin">
|
|
4069
4096
|
<option value="">All bins</option>
|
|
4070
4097
|
</select>
|
|
@@ -4074,6 +4101,7 @@ HTML = r"""<!doctype html>
|
|
|
4074
4101
|
</div>
|
|
4075
4102
|
</div>
|
|
4076
4103
|
<div id="reviewBinSummary" class="review-bin-summary">Loading analyzed clips…</div>
|
|
4104
|
+
<div id="reviewEntitiesCard" class="review-entities" style="display:none"></div>
|
|
4077
4105
|
<div id="reviewBinGrid" class="review-grid"></div>
|
|
4078
4106
|
<div id="reviewSearchResults" class="review-search-results" style="display:none"></div>
|
|
4079
4107
|
</div>
|
|
@@ -6201,6 +6229,13 @@ HTML = r"""<!doctype html>
|
|
|
6201
6229
|
syncPreferencesPanel();
|
|
6202
6230
|
applyPreferencesToControls(prefs);
|
|
6203
6231
|
renderVersionBadge();
|
|
6232
|
+
// Semantic search rides on a local text-embedding backend; only show
|
|
6233
|
+
// the toggle when one is detected (ollama nomic-embed-text or
|
|
6234
|
+
// sentence-transformers).
|
|
6235
|
+
if (state.boot?.capabilities?.embeddings?.text?.available) {
|
|
6236
|
+
const toggle = $('reviewSemanticToggle');
|
|
6237
|
+
if (toggle) toggle.style.display = '';
|
|
6238
|
+
}
|
|
6204
6239
|
// Paint the previous inventory immediately (stale) so a slow first
|
|
6205
6240
|
// /api/resolve/media — common with network source media — doesn't leave the
|
|
6206
6241
|
// panel blank. The live fetch below replaces it and clears the stale flag.
|
|
@@ -8083,6 +8118,22 @@ HTML = r"""<!doctype html>
|
|
|
8083
8118
|
renderReviewBin();
|
|
8084
8119
|
// Coverage runs in parallel — failures here must not block the clip grid.
|
|
8085
8120
|
refreshReadinessCard().catch(() => {});
|
|
8121
|
+
refreshEntitiesCard().catch(() => {});
|
|
8122
|
+
}
|
|
8123
|
+
|
|
8124
|
+
// Recurring people/places/props detected across the bin (Phase D).
|
|
8125
|
+
// Hidden until at least one labeled entity exists.
|
|
8126
|
+
async function refreshEntitiesCard() {
|
|
8127
|
+
const card = $('reviewEntitiesCard');
|
|
8128
|
+
if (!card) return;
|
|
8129
|
+
const data = await api('/api/entities').catch(() => null);
|
|
8130
|
+
const labeled = (data?.entities || []).filter(e => e.label);
|
|
8131
|
+
if (!labeled.length) { card.style.display = 'none'; return; }
|
|
8132
|
+
card.style.display = '';
|
|
8133
|
+
card.innerHTML = `<div class="review-entities-title">Recurring across this bin</div>
|
|
8134
|
+
<div class="review-entities-chips">${labeled.map(e =>
|
|
8135
|
+
`<span class="review-chip" title="${escapeHtml(e.description || '')}">${escapeHtml(e.label)} · ${e.kind || 'unknown'} · ${e.shot_count || e.cluster_size || 0} shots</span>`
|
|
8136
|
+
).join('')}</div>`;
|
|
8086
8137
|
}
|
|
8087
8138
|
|
|
8088
8139
|
async function refreshReadinessCard() {
|
|
@@ -8258,7 +8309,11 @@ HTML = r"""<!doctype html>
|
|
|
8258
8309
|
renderReviewBin();
|
|
8259
8310
|
return;
|
|
8260
8311
|
}
|
|
8261
|
-
const
|
|
8312
|
+
const semantic = !!$('reviewSemanticCheckbox')?.checked;
|
|
8313
|
+
const endpoint = semantic
|
|
8314
|
+
? `/api/search/semantic?q=${encodeURIComponent(query)}&limit=40`
|
|
8315
|
+
: `/api/index/query?q=${encodeURIComponent(query)}&limit=40`;
|
|
8316
|
+
const payload = await api(endpoint)
|
|
8262
8317
|
.catch(err => ({ success: false, error: String(err) }));
|
|
8263
8318
|
state.review.searchResults = payload;
|
|
8264
8319
|
renderReviewBin();
|
|
@@ -11083,6 +11138,11 @@ HTML = r"""<!doctype html>
|
|
|
11083
11138
|
runReviewSearch(q).catch(alertError);
|
|
11084
11139
|
}, 250);
|
|
11085
11140
|
});
|
|
11141
|
+
// Semantic toggle re-runs the current query through the embeddings index.
|
|
11142
|
+
$('reviewSemanticCheckbox')?.addEventListener('change', () => {
|
|
11143
|
+
const q = $('reviewSearchInput').value.trim();
|
|
11144
|
+
if (q) runReviewSearch(q).catch(alertError);
|
|
11145
|
+
});
|
|
11086
11146
|
// Bin dropdown.
|
|
11087
11147
|
$('reviewBinFilter').addEventListener('change', event => {
|
|
11088
11148
|
state.review.binFilter = event.target.value || '';
|
|
@@ -12470,6 +12530,57 @@ def _v2_load_analysis_db_first(project_root: str, clip_dir: str) -> Optional[Dic
|
|
|
12470
12530
|
return _v2_load_analysis(clip_dir)
|
|
12471
12531
|
|
|
12472
12532
|
|
|
12533
|
+
def _v2_semantic_search(project_root: str, q: str, *, limit: int = 20) -> Dict[str, Any]:
|
|
12534
|
+
"""Semantic search over the embeddings index, shaped like /api/index/query
|
|
12535
|
+
rows so the existing search-card renderer works unchanged."""
|
|
12536
|
+
text = (q or "").strip()
|
|
12537
|
+
if not text:
|
|
12538
|
+
return {"success": True, "results": []}
|
|
12539
|
+
try:
|
|
12540
|
+
from src.utils import embeddings, timeline_brain_db as tbd
|
|
12541
|
+
|
|
12542
|
+
found = embeddings.find_similar(project_root, text=text, kind="text", limit=limit)
|
|
12543
|
+
if not found.get("success"):
|
|
12544
|
+
return found
|
|
12545
|
+
conn = tbd.connect(project_root)
|
|
12546
|
+
resolve_ids: Dict[str, Optional[str]] = {}
|
|
12547
|
+
|
|
12548
|
+
def resolve_clip_id(clip_uuid: Optional[str]) -> Optional[str]:
|
|
12549
|
+
if not clip_uuid:
|
|
12550
|
+
return None
|
|
12551
|
+
if clip_uuid not in resolve_ids:
|
|
12552
|
+
row = conn.execute(
|
|
12553
|
+
"SELECT resolve_clip_id, clip_dir FROM clips WHERE clip_uuid = ?",
|
|
12554
|
+
(clip_uuid,),
|
|
12555
|
+
).fetchone()
|
|
12556
|
+
resolve_ids[clip_uuid] = (row["resolve_clip_id"] or row["clip_dir"]) if row else None
|
|
12557
|
+
return resolve_ids[clip_uuid]
|
|
12558
|
+
|
|
12559
|
+
rows: List[Dict[str, Any]] = []
|
|
12560
|
+
for hit in found.get("results") or []:
|
|
12561
|
+
entity_type = hit.get("entity_type")
|
|
12562
|
+
clip_uuid = hit.get("clip_uuid") or (hit.get("entity_uuid") if entity_type == "clip" else None)
|
|
12563
|
+
row: Dict[str, Any] = {
|
|
12564
|
+
"result_type": "transcript" if entity_type == "segment" else "semantic",
|
|
12565
|
+
"score": hit.get("score"),
|
|
12566
|
+
"clip_id": resolve_clip_id(clip_uuid),
|
|
12567
|
+
"clip_name": hit.get("clip_name"),
|
|
12568
|
+
}
|
|
12569
|
+
if entity_type == "shot":
|
|
12570
|
+
row["start_seconds"] = hit.get("time_seconds_start")
|
|
12571
|
+
row["summary"] = hit.get("description")
|
|
12572
|
+
elif entity_type == "segment":
|
|
12573
|
+
row["start_seconds"] = hit.get("start_seconds")
|
|
12574
|
+
row["summary"] = hit.get("text")
|
|
12575
|
+
else:
|
|
12576
|
+
row["summary"] = hit.get("summary")
|
|
12577
|
+
rows.append(row)
|
|
12578
|
+
rows = _v2_enrich_search_results(project_root, rows)
|
|
12579
|
+
return {"success": True, "query": text, "model": found.get("model"), "results": rows}
|
|
12580
|
+
except Exception as exc: # noqa: BLE001 — search must fail soft in the panel
|
|
12581
|
+
return {"success": False, "error": f"{type(exc).__name__}: {exc}"}
|
|
12582
|
+
|
|
12583
|
+
|
|
12473
12584
|
def _v2_clip_duration(report: Dict[str, Any]) -> Optional[float]:
|
|
12474
12585
|
marker_plan = report.get("clip_analysis_markers") if isinstance(report.get("clip_analysis_markers"), dict) else {}
|
|
12475
12586
|
duration = marker_plan.get("duration_seconds")
|
|
@@ -14316,6 +14427,22 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
14316
14427
|
payload["results"] = _v2_enrich_search_results(self.state.project_root, payload["results"])
|
|
14317
14428
|
self._json(payload)
|
|
14318
14429
|
return
|
|
14430
|
+
if path == "/api/entities":
|
|
14431
|
+
try:
|
|
14432
|
+
from src.utils import entities as _entities
|
|
14433
|
+
|
|
14434
|
+
self._json(_entities.list_entities(self.state.project_root))
|
|
14435
|
+
except Exception as exc: # noqa: BLE001 — panel reads fail soft
|
|
14436
|
+
self._json({"success": False, "error": f"{type(exc).__name__}: {exc}"})
|
|
14437
|
+
return
|
|
14438
|
+
if path == "/api/search/semantic":
|
|
14439
|
+
q = (query.get("q") or [""])[0]
|
|
14440
|
+
try:
|
|
14441
|
+
limit = int((query.get("limit") or ["20"])[0])
|
|
14442
|
+
except (TypeError, ValueError):
|
|
14443
|
+
limit = 20
|
|
14444
|
+
self._json(_v2_semantic_search(self.state.project_root, q, limit=limit))
|
|
14445
|
+
return
|
|
14319
14446
|
# ─── C6 timeline-history surface ───────────────────────────────
|
|
14320
14447
|
if path == "/api/timeline_versions":
|
|
14321
14448
|
self._json(list_timelines_with_versions(self.state.project_root))
|
package/src/granular/common.py
CHANGED
|
@@ -80,7 +80,7 @@ if not logging.getLogger().handlers:
|
|
|
80
80
|
handlers=[logging.StreamHandler()],
|
|
81
81
|
)
|
|
82
82
|
|
|
83
|
-
VERSION = "2.
|
|
83
|
+
VERSION = "2.44.0"
|
|
84
84
|
logger = logging.getLogger("davinci-resolve-mcp")
|
|
85
85
|
logger.info(f"Starting DaVinci Resolve MCP Server v{VERSION}")
|
|
86
86
|
logger.info(f"Detected platform: {get_platform()}")
|
package/src/server.py
CHANGED
|
@@ -11,7 +11,7 @@ Usage:
|
|
|
11
11
|
python src/server.py --full # Start the 341-tool granular server instead
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
|
-
VERSION = "2.
|
|
14
|
+
VERSION = "2.44.0"
|
|
15
15
|
|
|
16
16
|
import base64
|
|
17
17
|
import os
|
|
@@ -15024,6 +15024,15 @@ async def media_analysis(action: str, params: Optional[Dict[str, Any]] = None, c
|
|
|
15024
15024
|
"deepen",
|
|
15025
15025
|
"commit_shot_vision",
|
|
15026
15026
|
"vision_pending_sweep",
|
|
15027
|
+
# Phase C — embeddings + similarity.
|
|
15028
|
+
"build_embeddings",
|
|
15029
|
+
"find_similar",
|
|
15030
|
+
# Phase D — cross-clip entities + bin briefing v2.
|
|
15031
|
+
"detect_entities",
|
|
15032
|
+
"commit_entities",
|
|
15033
|
+
"list_entities",
|
|
15034
|
+
"prepare_bin_briefing",
|
|
15035
|
+
"commit_bin_summary",
|
|
15027
15036
|
}:
|
|
15028
15037
|
root = resolve_media_analysis_output_root(
|
|
15029
15038
|
project_name=project_name,
|
|
@@ -15119,6 +15128,66 @@ async def media_analysis(action: str, params: Optional[Dict[str, Any]] = None, c
|
|
|
15119
15128
|
max_age_days=p.get("max_age_days") or p.get("maxAgeDays"),
|
|
15120
15129
|
reoffer=_media_analysis_bool(p.get("reoffer"), False),
|
|
15121
15130
|
)
|
|
15131
|
+
# Phase C — embeddings + similarity (local compute; no token caps).
|
|
15132
|
+
if action == "build_embeddings":
|
|
15133
|
+
from src.utils import embeddings
|
|
15134
|
+
kinds = p.get("kinds") or p.get("kind") or ["text"]
|
|
15135
|
+
if isinstance(kinds, str):
|
|
15136
|
+
kinds = [kinds]
|
|
15137
|
+
return embeddings.build_embeddings(
|
|
15138
|
+
project_root,
|
|
15139
|
+
kinds=[str(k).strip().lower() for k in kinds],
|
|
15140
|
+
clip_ref=p.get("clip_id") or p.get("clipId") or p.get("clip_dir") or p.get("clipDir"),
|
|
15141
|
+
include_segments=_media_analysis_bool(p.get("include_segments", p.get("includeSegments")), True),
|
|
15142
|
+
max_frames_per_clip=int(p.get("max_frames_per_clip") or p.get("maxFramesPerClip") or 16),
|
|
15143
|
+
)
|
|
15144
|
+
if action == "find_similar":
|
|
15145
|
+
from src.utils import embeddings
|
|
15146
|
+
entity_types = p.get("entity_types") or p.get("entityTypes")
|
|
15147
|
+
if isinstance(entity_types, str):
|
|
15148
|
+
entity_types = [entity_types]
|
|
15149
|
+
return embeddings.find_similar(
|
|
15150
|
+
project_root,
|
|
15151
|
+
text=p.get("text") or p.get("query"),
|
|
15152
|
+
clip_ref=p.get("clip_id") or p.get("clipId") or p.get("clip_dir") or p.get("clipDir"),
|
|
15153
|
+
shot_index=p.get("shot_index") if p.get("shot_index") is not None else p.get("shotIndex"),
|
|
15154
|
+
shot_uuid=p.get("shot_uuid") or p.get("shotUuid"),
|
|
15155
|
+
kind=p.get("kind") or "text",
|
|
15156
|
+
entity_types=entity_types,
|
|
15157
|
+
limit=int(p.get("limit") or 10),
|
|
15158
|
+
)
|
|
15159
|
+
# Phase D — cross-clip entities + bin briefing v2.
|
|
15160
|
+
if action == "detect_entities":
|
|
15161
|
+
from src.utils import entities
|
|
15162
|
+
return entities.detect_entities(
|
|
15163
|
+
project_root,
|
|
15164
|
+
threshold=float(p.get("threshold") or entities.DEFAULT_CLUSTER_THRESHOLD),
|
|
15165
|
+
min_cluster_size=int(p.get("min_cluster_size") or p.get("minClusterSize") or entities.DEFAULT_MIN_CLUSTER_SIZE),
|
|
15166
|
+
max_clusters=int(p.get("max_clusters") or p.get("maxClusters") or 24),
|
|
15167
|
+
job_id=p.get("job_id") or p.get("jobId"),
|
|
15168
|
+
)
|
|
15169
|
+
if action == "commit_entities":
|
|
15170
|
+
from src.utils import entities
|
|
15171
|
+
return entities.commit_entities(
|
|
15172
|
+
project_root,
|
|
15173
|
+
entities_payload=p.get("entities"),
|
|
15174
|
+
vision_token=p.get("vision_token") or p.get("visionToken"),
|
|
15175
|
+
author=p.get("author") or "host_chat",
|
|
15176
|
+
)
|
|
15177
|
+
if action == "list_entities":
|
|
15178
|
+
from src.utils import entities
|
|
15179
|
+
return entities.list_entities(project_root, kind=p.get("kind"))
|
|
15180
|
+
if action == "prepare_bin_briefing":
|
|
15181
|
+
from src.utils import entities
|
|
15182
|
+
return entities.prepare_bin_briefing(project_root)
|
|
15183
|
+
if action == "commit_bin_summary":
|
|
15184
|
+
from src.utils import entities
|
|
15185
|
+
return entities.commit_bin_summary(
|
|
15186
|
+
project_root,
|
|
15187
|
+
briefing=p.get("briefing") or p.get("summary"),
|
|
15188
|
+
briefing_token=p.get("briefing_token") or p.get("briefingToken"),
|
|
15189
|
+
author=p.get("author") or "host_chat",
|
|
15190
|
+
)
|
|
15122
15191
|
if action in {"build_index", "rebuild_index"}:
|
|
15123
15192
|
return build_analysis_index(project_root, index_path=p.get("index_path") or p.get("indexPath"))
|
|
15124
15193
|
if action == "index_status":
|
|
@@ -15533,6 +15602,13 @@ async def media_analysis(action: str, params: Optional[Dict[str, Any]] = None, c
|
|
|
15533
15602
|
"deepen",
|
|
15534
15603
|
"commit_shot_vision",
|
|
15535
15604
|
"vision_pending_sweep",
|
|
15605
|
+
"build_embeddings",
|
|
15606
|
+
"find_similar",
|
|
15607
|
+
"detect_entities",
|
|
15608
|
+
"commit_entities",
|
|
15609
|
+
"list_entities",
|
|
15610
|
+
"prepare_bin_briefing",
|
|
15611
|
+
"commit_bin_summary",
|
|
15536
15612
|
"get_caps",
|
|
15537
15613
|
"set_caps_preset",
|
|
15538
15614
|
"get_usage",
|