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.
- package/CHANGELOG.md +104 -0
- package/README.md +1 -1
- package/install.py +1 -1
- package/package.json +1 -1
- package/src/granular/common.py +1 -1
- package/src/server.py +516 -189
- package/src/utils/analysis_store.py +39 -0
- package/src/utils/background_jobs.py +108 -0
- package/src/utils/deep_vision.py +1 -30
- package/src/utils/strata.py +87 -20
- package/src/utils/strata_analyzers.py +135 -91
- package/src/utils/strata_queries.py +43 -23
- package/src/utils/strata_story.py +4 -10
- package/src/utils/timeline_brain_db.py +18 -1
|
@@ -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
|
-
|
|
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
|
|
211
|
-
"
|
|
212
|
-
|
|
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
|
|
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
|
|
228
|
-
|
|
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
|
-
|
|
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":
|
|
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
|
-
|
|
425
|
-
|
|
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(
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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 =
|
|
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(
|