davinci-resolve-mcp 2.61.0 → 2.62.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.
@@ -27,16 +27,8 @@ def _norm_word(word: str) -> str:
27
27
 
28
28
 
29
29
  def _resolve(project_root: str, clip_ref: Any) -> Tuple[Optional[str], Optional[Dict[str, Any]]]:
30
- from src.utils import analysis_store
31
-
32
- conn = timeline_brain_db.connect(project_root)
33
- clip_uuid = analysis_store.resolve_clip_uuid(conn, clip_ref)
34
- if not clip_uuid:
35
- return None, {
36
- "success": False,
37
- "error": f"Unknown clip ref: {clip_ref!r} (older analysis root? run db_ingest first)",
38
- }
39
- return clip_uuid, None
30
+ _conn, clip, err = strata.resolve_clip(project_root, clip_ref)
31
+ return (clip["clip_uuid"] if clip else None), err
40
32
 
41
33
 
42
34
  def _finite(values: Sequence[float]) -> List[float]:
@@ -204,28 +196,45 @@ def _clip_bundle(
204
196
  end: Optional[float],
205
197
  *,
206
198
  include_curve_values: bool = False,
199
+ cache: Optional[Dict[str, Dict[str, Any]]] = None,
207
200
  ) -> Dict[str, Any]:
208
- """Everything the strata know about one clip window, joined."""
201
+ """Everything the strata know about one clip window, joined.
202
+
203
+ ``cache`` (keyed by clip_uuid) memoizes the window-independent reads —
204
+ the clips row, present-track lists, and full curve blobs — so callers
205
+ building many bundles for the same clip (word-find hits cluster by clip)
206
+ unpack each curve once. Windowed reads stay per call.
207
+ """
208
+ memo = cache.setdefault(clip_uuid, {}) if cache is not None else {}
209
209
  bundle: Dict[str, Any] = {"clip_uuid": clip_uuid}
210
- row = conn.execute(
211
- "SELECT clip_name, duration_seconds, fps FROM clips WHERE clip_uuid = ?", (clip_uuid,)
212
- ).fetchone()
210
+ if "row" not in memo:
211
+ memo["row"] = conn.execute(
212
+ "SELECT clip_name, duration_seconds, fps FROM clips WHERE clip_uuid = ?", (clip_uuid,)
213
+ ).fetchone()
214
+ row = memo["row"]
213
215
  if row:
214
216
  bundle["clip_name"] = row["clip_name"]
215
217
  bundle["fps"] = row["fps"]
216
218
 
217
219
  bundle["words"] = strata.read_words(conn, clip_uuid, start_seconds=start, end_seconds=end)
218
220
 
221
+ if "event_tracks" not in memo:
222
+ memo["event_tracks"] = _present_tracks(conn, clip_uuid, "events", _EVENT_TRACK_ORDER)
219
223
  events: Dict[str, List[Dict[str, Any]]] = {}
220
- for track in _present_tracks(conn, clip_uuid, "events", _EVENT_TRACK_ORDER):
224
+ for track in memo["event_tracks"]:
221
225
  hits = strata.read_events(conn, clip_uuid, track, start_seconds=start, end_seconds=end)
222
226
  if hits:
223
227
  events[track] = hits
224
228
  bundle["events"] = events
225
229
 
230
+ if "curve_tracks" not in memo:
231
+ memo["curve_tracks"] = _present_tracks(conn, clip_uuid, "curves", _CURVE_TRACK_ORDER)
232
+ loaded_curves = memo.setdefault("curves_by_track", {})
226
233
  curves: Dict[str, Any] = {}
227
- for track in _present_tracks(conn, clip_uuid, "curves", _CURVE_TRACK_ORDER):
228
- curve = strata.read_curve(conn, clip_uuid, track)
234
+ for track in memo["curve_tracks"]:
235
+ if track not in loaded_curves:
236
+ loaded_curves[track] = strata.read_curve(conn, clip_uuid, track)
237
+ curve = loaded_curves[track]
229
238
  if curve is None:
230
239
  continue
231
240
  lo = start if start is not None else 0.0
@@ -303,6 +312,7 @@ def strata_query(
303
312
  sql += " ORDER BY w.clip_uuid, w.start_seconds LIMIT ?"
304
313
  args.append(int(limit))
305
314
  hits = []
315
+ bundle_cache: Dict[str, Dict[str, Any]] = {}
306
316
  for row in conn.execute(sql, args).fetchall():
307
317
  t = row["start_seconds"]
308
318
  hit: Dict[str, Any] = {
@@ -318,6 +328,7 @@ def strata_query(
318
328
  max(0.0, t - context_seconds),
319
329
  t + context_seconds,
320
330
  include_curve_values=include_curve_values,
331
+ cache=bundle_cache,
321
332
  )
322
333
  hits.append(hit)
323
334
  return {"success": True, "mode": "word_find", "match": match_word, "hits": hits}
@@ -405,10 +416,17 @@ def timeline_strata(
405
416
 
406
417
  placements = []
407
418
  unresolved = []
419
+ # A source clip reused across many cuts resolves and bundles once: the
420
+ # whole-clip bundle (start=end=None) is identical for every placement.
421
+ resolve_memo: Dict[str, Optional[str]] = {}
422
+ bundle_memo: Dict[str, Dict[str, Any]] = {}
408
423
  for row in conn.execute(sql, args).fetchall():
409
- clip_uuid = analysis_store.resolve_clip_uuid(conn, row["media_pool_item_id"])
424
+ media_id = row["media_pool_item_id"]
425
+ if media_id not in resolve_memo:
426
+ resolve_memo[media_id] = analysis_store.resolve_clip_uuid(conn, media_id)
427
+ clip_uuid = resolve_memo[media_id]
410
428
  placement = {
411
- "media_pool_item_id": row["media_pool_item_id"],
429
+ "media_pool_item_id": media_id,
412
430
  "track_type": row["track_type"],
413
431
  "track_index": row["track_index"],
414
432
  "record_in_frame": row["in_frame"],
@@ -421,11 +439,13 @@ def timeline_strata(
421
439
  placement["record_in_seconds"] = round((row["in_frame"] - start_frame) / fps, 3)
422
440
  placement["record_out_seconds"] = round((row["out_frame"] - start_frame) / fps, 3)
423
441
  if clip_uuid:
424
- placement["strata"] = _clip_bundle(
425
- conn, clip_uuid, None, None, include_curve_values=include_curve_values
426
- )
442
+ if clip_uuid not in bundle_memo:
443
+ bundle_memo[clip_uuid] = _clip_bundle(
444
+ conn, clip_uuid, None, None, include_curve_values=include_curve_values
445
+ )
446
+ placement["strata"] = bundle_memo[clip_uuid]
427
447
  else:
428
- unresolved.append(row["media_pool_item_id"])
448
+ unresolved.append(media_id)
429
449
  placements.append(placement)
430
450
 
431
451
  return {
@@ -84,16 +84,10 @@ def _beat_uuid() -> str:
84
84
 
85
85
 
86
86
  def _resolve(project_root: str, clip_ref: Any):
87
- from src.utils import analysis_store
88
-
89
- conn = timeline_brain_db.connect(project_root)
90
- clip_uuid = analysis_store.resolve_clip_uuid(conn, clip_ref)
91
- if not clip_uuid:
92
- return None, None, {
93
- "success": False,
94
- "error": f"Unknown clip ref: {clip_ref!r} (older analysis root? run db_ingest first)",
95
- }
96
- return conn, clip_uuid, None
87
+ conn, clip, err = strata.resolve_clip(project_root, clip_ref)
88
+ if err:
89
+ return None, None, err
90
+ return conn, clip["clip_uuid"], None
97
91
 
98
92
 
99
93
  def plan_story_beats(project_root: str, clip_ref: Any) -> Dict[str, Any]:
@@ -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 = 14
32
+ SCHEMA_VERSION = 15
33
33
  DB_FILENAME = "timeline_brain.sqlite"
34
34
  SOUL_DIRNAME = "_soul"
35
35
 
@@ -924,6 +924,23 @@ def _migrate_v14_timeline_version_timebase(conn: sqlite3.Connection) -> None:
924
924
  conn.execute("ALTER TABLE timeline_versions ADD COLUMN start_frame INTEGER")
925
925
 
926
926
 
927
+ @register_migration(15)
928
+ def _migrate_v15_events_span_index(conn: sqlite3.Connection) -> None:
929
+ """Index (clip_uuid, track, duration_seconds) on events.
930
+
931
+ strata.read_events bounds its windowed lower edge by the track's longest
932
+ span (the sargable form of span-overlap); this index makes that
933
+ MAX(duration_seconds) probe a single b-tree descent instead of a track
934
+ scan.
935
+ """
936
+ conn.executescript(
937
+ """
938
+ CREATE INDEX IF NOT EXISTS ix_events_clip_track_span
939
+ ON events(clip_uuid, track, duration_seconds);
940
+ """
941
+ )
942
+
943
+
927
944
  def latest_version(conn: sqlite3.Connection, timeline_name: str) -> Optional[int]:
928
945
  """Return the highest archived version number for `timeline_name`, or None."""
929
946
  row = conn.execute(