davinci-resolve-mcp 2.41.0 → 2.43.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 +54 -0
- package/README.md +1 -1
- package/docs/SKILL.md +39 -2
- package/docs/guides/control-panel.md +12 -3
- package/install.py +1 -1
- package/package.json +1 -1
- package/src/analysis_dashboard.py +81 -2
- package/src/granular/common.py +1 -1
- package/src/server.py +77 -1
- package/src/utils/analysis_store.py +4 -1
- package/src/utils/deep_vision.py +643 -0
- package/src/utils/embeddings.py +668 -0
- package/src/utils/media_analysis.py +94 -1
- package/src/utils/timeline_brain_db.py +33 -1
|
@@ -1436,6 +1436,28 @@ TOOL_INSTALL: Dict[str, Dict[str, Any]] = {
|
|
|
1436
1436
|
"verify": "whisper --help",
|
|
1437
1437
|
"notes": "Pure-Python reference implementation. Choose this OR whisper_cpp OR mlx_whisper.",
|
|
1438
1438
|
},
|
|
1439
|
+
"ollama_embeddings": {
|
|
1440
|
+
"label": "ollama + nomic-embed-text",
|
|
1441
|
+
"bundle": "embeddings",
|
|
1442
|
+
"required_for": ["semantic search (text embeddings)", "find_similar"],
|
|
1443
|
+
"commands": {
|
|
1444
|
+
"macos": "brew install ollama && ollama pull nomic-embed-text",
|
|
1445
|
+
"linux": "curl -fsSL https://ollama.com/install.sh | sh && ollama pull nomic-embed-text",
|
|
1446
|
+
"windows": "winget install Ollama.Ollama, then: ollama pull nomic-embed-text",
|
|
1447
|
+
},
|
|
1448
|
+
"verify": "ollama list",
|
|
1449
|
+
"notes": "Local embedding model (~270 MB). sentence-transformers is an alternative text backend.",
|
|
1450
|
+
},
|
|
1451
|
+
"open_clip": {
|
|
1452
|
+
"label": "open_clip (CLIP visual embeddings)",
|
|
1453
|
+
"bundle": "embeddings",
|
|
1454
|
+
"required_for": ["visual similarity (find_similar kind=visual)", "cross-clip entity clustering"],
|
|
1455
|
+
"commands": {
|
|
1456
|
+
"all": "pip install open_clip_torch",
|
|
1457
|
+
},
|
|
1458
|
+
"verify": "python -c \"import open_clip\"",
|
|
1459
|
+
"notes": "Needs torch. Model weights (~350 MB) download on first use.",
|
|
1460
|
+
},
|
|
1439
1461
|
"whisper_cpp": {
|
|
1440
1462
|
"label": "whisper.cpp",
|
|
1441
1463
|
"bundle": "transcription",
|
|
@@ -1585,6 +1607,17 @@ def detect_capabilities(env: Optional[Dict[str, str]] = None) -> Dict[str, Any]:
|
|
|
1585
1607
|
|
|
1586
1608
|
sync_events = detect_sync_event_capabilities()
|
|
1587
1609
|
|
|
1610
|
+
# Phase C — embedding backends (detected like the whisper backends; the
|
|
1611
|
+
# ollama probe is a short local HTTP call and fails fast when not serving).
|
|
1612
|
+
try:
|
|
1613
|
+
from src.utils import embeddings as _embeddings
|
|
1614
|
+
|
|
1615
|
+
embedding_caps = _embeddings.detect_embedding_capabilities()
|
|
1616
|
+
except Exception: # noqa: BLE001 — detection must never break capabilities
|
|
1617
|
+
embedding_caps = {"text": {"available": False, "backends": []},
|
|
1618
|
+
"visual": {"available": False, "backends": []},
|
|
1619
|
+
"install_guidance": {}}
|
|
1620
|
+
|
|
1588
1621
|
platform_id, machine = _runtime_platform_id()
|
|
1589
1622
|
|
|
1590
1623
|
def _tool_entry(name: str, available: bool, extra: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
@@ -1607,7 +1640,18 @@ def detect_capabilities(env: Optional[Dict[str, str]] = None) -> Dict[str, Any]:
|
|
|
1607
1640
|
"whisper_cpp": _tool_entry("whisper_cpp", bool(whisper_cpp), {"path": whisper_cpp}),
|
|
1608
1641
|
"mlx_whisper": _tool_entry("mlx_whisper", bool(mlx_whisper), {"python_module": "mlx_whisper"}),
|
|
1609
1642
|
"opencv": _tool_entry("opencv", bool(cv2), {"python_module": "cv2"}),
|
|
1643
|
+
"ollama_embeddings": _tool_entry(
|
|
1644
|
+
"ollama_embeddings",
|
|
1645
|
+
bool(embedding_caps.get("text", {}).get("available")),
|
|
1646
|
+
{"backends": embedding_caps.get("text", {}).get("backends", [])},
|
|
1647
|
+
),
|
|
1648
|
+
"open_clip": _tool_entry(
|
|
1649
|
+
"open_clip",
|
|
1650
|
+
bool(embedding_caps.get("visual", {}).get("available")),
|
|
1651
|
+
{"python_module": "open_clip"},
|
|
1652
|
+
),
|
|
1610
1653
|
},
|
|
1654
|
+
"embeddings": embedding_caps,
|
|
1611
1655
|
"transcription": {
|
|
1612
1656
|
"available": bool(whisper_cli or whisper_cpp or mlx_whisper),
|
|
1613
1657
|
"backends": [
|
|
@@ -4265,7 +4309,7 @@ def build_host_chat_paths_payload(
|
|
|
4265
4309
|
if refusal is not None:
|
|
4266
4310
|
return refusal
|
|
4267
4311
|
|
|
4268
|
-
|
|
4312
|
+
payload: Dict[str, Any] = {
|
|
4269
4313
|
"success": True,
|
|
4270
4314
|
"status": "pending_host_analysis",
|
|
4271
4315
|
"provider": HOST_CHAT_PATHS_PROVIDER,
|
|
@@ -4327,6 +4371,21 @@ def build_host_chat_paths_payload(
|
|
|
4327
4371
|
"the clip's analysis directory; commit_vision finishes the run."
|
|
4328
4372
|
),
|
|
4329
4373
|
}
|
|
4374
|
+
# Phase B — deep depth: each shot_descriptions entry must additionally
|
|
4375
|
+
# carry the per-shot field groups. The extra keys flow through
|
|
4376
|
+
# commit_vision → canonical blob → subjective_fields rows unchanged.
|
|
4377
|
+
if str(options.get("depth") or "").lower() == "deep":
|
|
4378
|
+
from src.utils import deep_vision as _deep_vision
|
|
4379
|
+
|
|
4380
|
+
payload["deep_shot_schema"] = _deep_vision.deep_shot_schema()
|
|
4381
|
+
payload["deep_schema_reference"] = _deep_vision.DEEP_SHOT_SCHEMA_REFERENCE
|
|
4382
|
+
payload["instructions"] += (
|
|
4383
|
+
" DEEP PASS: in addition to `description`, every shot_descriptions "
|
|
4384
|
+
"entry MUST include the field groups in `deep_shot_schema` (visual, "
|
|
4385
|
+
"content, production, editorial, cuttability, confidence), using the "
|
|
4386
|
+
"enum values verbatim and 'unknown'/null when frame evidence is thin."
|
|
4387
|
+
)
|
|
4388
|
+
return payload
|
|
4330
4389
|
|
|
4331
4390
|
|
|
4332
4391
|
def _vision_analysis(record: Dict[str, Any], motion: Dict[str, Any], options: Dict[str, Any], artifacts: Dict[str, Any], capabilities: Dict[str, Any]) -> Dict[str, Any]:
|
|
@@ -5096,6 +5155,9 @@ async def execute_plan_async(
|
|
|
5096
5155
|
# Same for project_root — caps recording needs it to address the per-
|
|
5097
5156
|
# project usage DB; falling back to the plan's output_root is fine.
|
|
5098
5157
|
"project_root": params.get("project_root") or output_root,
|
|
5158
|
+
# Phase B — depth threads into the vision payload builder so deep runs
|
|
5159
|
+
# carry the per-shot field-group schema.
|
|
5160
|
+
"depth": plan.get("depth", DEFAULT_DEPTH),
|
|
5099
5161
|
}
|
|
5100
5162
|
keep_frame_artifacts_for_vision = vision_uses_chat_context(options, caps)
|
|
5101
5163
|
depth = plan.get("depth", DEFAULT_DEPTH)
|
|
@@ -5114,6 +5176,37 @@ async def execute_plan_async(
|
|
|
5114
5176
|
}
|
|
5115
5177
|
_write_json(os.path.join(output_root, "capabilities.json"), caps)
|
|
5116
5178
|
|
|
5179
|
+
# Phase B — deep depth is opt-in with an explicit cost estimate first.
|
|
5180
|
+
# The per-shot field-group pass multiplies vision spend, so the first call
|
|
5181
|
+
# returns the estimate; re-call with confirm_deep=true to run. Caps still
|
|
5182
|
+
# apply downstream — confirmation does not bypass budgets.
|
|
5183
|
+
if (
|
|
5184
|
+
depth == "deep"
|
|
5185
|
+
and vision_uses_chat_context(options, caps)
|
|
5186
|
+
and not _coerce_bool(params.get("confirm_deep") or params.get("confirmDeep"), default=False)
|
|
5187
|
+
):
|
|
5188
|
+
executing = [
|
|
5189
|
+
clip for clip in plan.get("clips", [])
|
|
5190
|
+
if not (clip.get("skip_execution") and (clip.get("existing_report") or {}).get("path"))
|
|
5191
|
+
]
|
|
5192
|
+
estimated_frames = sum(int(c.get("analysis_keyframe_budget") or 0) for c in executing)
|
|
5193
|
+
return {
|
|
5194
|
+
"success": True,
|
|
5195
|
+
"status": "confirmation_required",
|
|
5196
|
+
"reason": "deep_depth_cost_estimate",
|
|
5197
|
+
"estimate": {
|
|
5198
|
+
"clip_count": len(executing),
|
|
5199
|
+
"estimated_frames": estimated_frames,
|
|
5200
|
+
"estimated_vision_tokens": estimated_frames * AVG_VISION_TOKENS_PER_FRAME,
|
|
5201
|
+
"tokens_per_frame_assumption": AVG_VISION_TOKENS_PER_FRAME,
|
|
5202
|
+
},
|
|
5203
|
+
"note": (
|
|
5204
|
+
"Deep analysis fills per-shot Visual/Content/Editorial field groups "
|
|
5205
|
+
"and costs vision tokens accordingly. Re-call the same analyze action "
|
|
5206
|
+
"with confirm_deep=true to proceed, or drop depth to 'standard'."
|
|
5207
|
+
),
|
|
5208
|
+
}
|
|
5209
|
+
|
|
5117
5210
|
for clip_plan in plan.get("clips", []):
|
|
5118
5211
|
record = clip_plan["record"]
|
|
5119
5212
|
artifacts = clip_plan["artifacts"]
|
|
@@ -29,7 +29,7 @@ from typing import Callable, Dict, Iterator, Optional, Tuple
|
|
|
29
29
|
|
|
30
30
|
logger = logging.getLogger("resolve-mcp.timeline-brain-db")
|
|
31
31
|
|
|
32
|
-
SCHEMA_VERSION =
|
|
32
|
+
SCHEMA_VERSION = 10
|
|
33
33
|
DB_FILENAME = "timeline_brain.sqlite"
|
|
34
34
|
SOUL_DIRNAME = "_soul"
|
|
35
35
|
|
|
@@ -687,6 +687,38 @@ def _migrate_v9_analysis_core(conn: sqlite3.Connection) -> None:
|
|
|
687
687
|
)
|
|
688
688
|
|
|
689
689
|
|
|
690
|
+
@register_migration(10)
|
|
691
|
+
def _migrate_v10_embeddings(conn: sqlite3.Connection) -> None:
|
|
692
|
+
"""C3 — embeddings for similarity search (Phase C of the analysis program).
|
|
693
|
+
|
|
694
|
+
One row per (entity, kind, model). Vectors are float32 BLOBs; similarity
|
|
695
|
+
is brute-force cosine in the app layer (thousands of rows, not millions).
|
|
696
|
+
`content_hash` fingerprints the embedded content so re-runs only re-embed
|
|
697
|
+
entities whose text/frames changed.
|
|
698
|
+
"""
|
|
699
|
+
conn.executescript(
|
|
700
|
+
"""
|
|
701
|
+
CREATE TABLE IF NOT EXISTS embeddings (
|
|
702
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
703
|
+
entity_type TEXT NOT NULL CHECK(entity_type IN ('clip', 'shot', 'frame', 'segment')),
|
|
704
|
+
entity_uuid TEXT NOT NULL,
|
|
705
|
+
embedding_kind TEXT NOT NULL, -- 'text' | 'visual'
|
|
706
|
+
model_name TEXT NOT NULL,
|
|
707
|
+
dimension INTEGER NOT NULL,
|
|
708
|
+
vector BLOB NOT NULL,
|
|
709
|
+
content_hash TEXT,
|
|
710
|
+
computed_at TEXT NOT NULL,
|
|
711
|
+
UNIQUE(entity_type, entity_uuid, embedding_kind, model_name)
|
|
712
|
+
);
|
|
713
|
+
|
|
714
|
+
CREATE INDEX IF NOT EXISTS ix_embeddings_kind
|
|
715
|
+
ON embeddings(embedding_kind, model_name);
|
|
716
|
+
CREATE INDEX IF NOT EXISTS ix_embeddings_entity
|
|
717
|
+
ON embeddings(entity_type, entity_uuid);
|
|
718
|
+
"""
|
|
719
|
+
)
|
|
720
|
+
|
|
721
|
+
|
|
690
722
|
def latest_version(conn: sqlite3.Connection, timeline_name: str) -> Optional[int]:
|
|
691
723
|
"""Return the highest archived version number for `timeline_name`, or None."""
|
|
692
724
|
row = conn.execute(
|