davinci-resolve-mcp 2.40.0 → 2.42.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 +68 -0
- package/README.md +1 -1
- package/docs/SKILL.md +39 -2
- package/docs/guides/control-panel.md +7 -2
- package/install.py +1 -1
- package/package.json +1 -1
- package/src/analysis_dashboard.py +25 -5
- package/src/granular/common.py +1 -1
- package/src/server.py +133 -1
- package/src/utils/analysis_store.py +927 -0
- package/src/utils/deep_vision.py +643 -0
- package/src/utils/media_analysis.py +77 -1
- package/src/utils/timeline_brain_db.py +179 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,74 @@
|
|
|
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.42.0
|
|
6
|
+
|
|
7
|
+
Deep shot-level vision tier — Phase B of the analysis + edit-engine program.
|
|
8
|
+
Opt-in, estimate-first per-shot field filling for the Visual / Content /
|
|
9
|
+
Production / Editorial / Cuttability groups the shot pages already render.
|
|
10
|
+
|
|
11
|
+
- **Added** `src/utils/deep_vision.py` and three `media_analysis` actions:
|
|
12
|
+
`deepen` (estimate → confirm_token → deferred host-vision payload per
|
|
13
|
+
clip/shot), `commit_shot_vision` (writes `vision_deep_v1` provenance rows,
|
|
14
|
+
updates the canonical blob, re-exports analysis.json in lockstep), and
|
|
15
|
+
`vision_pending_sweep` (lists clips stuck in `pending_host_analysis`;
|
|
16
|
+
`reoffer=true` returns the stored payload, `expire=true` stamps them).
|
|
17
|
+
- **Added** deep depth to the analyze flow: `depth="deep"` extends the
|
|
18
|
+
deferred payload with the per-shot schema and requires `confirm_deep=true`
|
|
19
|
+
after a token-cost estimate. Caps pre-call refusal applies on both paths.
|
|
20
|
+
- **Added** panel affordances: `Deepen analysis` (clip view) and `Deepen this
|
|
21
|
+
shot` (shot view) copy ready-made chat prompts, per the chat-first UX.
|
|
22
|
+
Shots with no sampled frames on disk get 1–2 frames re-extracted via
|
|
23
|
+
ffmpeg, downscaled per caps (source media stays read-only).
|
|
24
|
+
- **Fixed** a provenance bug in the analysis store: a source re-deriving an
|
|
25
|
+
unchanged value no longer re-attributes the row (a deep pass would
|
|
26
|
+
otherwise claim every untouched field as `vision_deep_v1`).
|
|
27
|
+
- **Validation**: full offline suite (1081 tests; 15 new), and a real
|
|
28
|
+
end-to-end deep pass on the 2026-05-17 sample clip — estimate → confirm →
|
|
29
|
+
frames read by the host chat → commit → rows/blob/export parity → fields
|
|
30
|
+
visible in the panel shot view. Control-panel guide screenshots
|
|
31
|
+
regenerated from the live panel.
|
|
32
|
+
|
|
33
|
+
## What's New in v2.41.0
|
|
34
|
+
|
|
35
|
+
DB-canonical clip analysis (C1) — Phase A of the analysis + edit-engine
|
|
36
|
+
program. The per-project SQLite DB is now the source of truth for clip
|
|
37
|
+
analysis; `analysis.json` becomes a derived export written in lockstep.
|
|
38
|
+
|
|
39
|
+
- **Added** schema v9 to the per-project timeline-brain DB: `clips`,
|
|
40
|
+
`clip_aliases`, `analysis_reports` (canonical full payload), `shots`,
|
|
41
|
+
`subjective_fields` + `field_changelog` (per-field provenance),
|
|
42
|
+
`transcript_segments`, `frames`, and `qc_observations`.
|
|
43
|
+
- **Added** `src/utils/analysis_store.py`: transactional ingest, export with
|
|
44
|
+
human-correction overlay (human rows always win and survive re-analysis),
|
|
45
|
+
alias-based clip lookup, shot ids stable under one-second boundary jitter,
|
|
46
|
+
and a round-trip guard verified against a real sample analysis root.
|
|
47
|
+
- **Added** `media_analysis` actions `db_status` (schema version + row
|
|
48
|
+
counts) and `db_ingest` (one-shot migration of existing JSON reports and
|
|
49
|
+
`corrections.json` sidecars into the DB).
|
|
50
|
+
- **Changed** the analysis write path: `execute_plan` and `commit_vision`
|
|
51
|
+
write DB rows first, then export the JSON. Panel clip/shot endpoints read
|
|
52
|
+
DB-first with JSON fallback for pre-v9 reports and job-linked report dirs.
|
|
53
|
+
- **Changed** `update_clip_field` / `update_shot_field` / `revert_field` to
|
|
54
|
+
mirror corrections into the DB as row-level provenance (the
|
|
55
|
+
`corrections.json` sidecar remains for compatibility).
|
|
56
|
+
- **Fixed** eight V2 actions that were unreachable from MCP dispatch since
|
|
57
|
+
v2.24.0 (`get_panel_state`, `set_panel_state`, `session_start_context`,
|
|
58
|
+
`update_shot_field`, `update_clip_field`, `get_field_history`,
|
|
59
|
+
`revert_field`, `list_corrections`): they were checked inside the
|
|
60
|
+
project-root dispatch block but missing from its membership set. The
|
|
61
|
+
control panel proxied to the helpers directly, which masked it.
|
|
62
|
+
- **Fixed** the action-list drift guard to inspect async tool functions
|
|
63
|
+
(media_analysis had drifted unchecked) and to fail on actions that are
|
|
64
|
+
unreachable inside membership blocks — the exact class above. Four
|
|
65
|
+
reachable-but-unadvertised actions (`coverage_report`,
|
|
66
|
+
`get_resolve_ai_usage`, `get_ai_governance`, `set_ai_governance`) are now
|
|
67
|
+
listed in the unknown-action error.
|
|
68
|
+
- **Validation**: full offline suite (1066 tests; 12 new), round-trip guard
|
|
69
|
+
on the real 2026-05-17 sample root, and a live headless pipeline run on
|
|
70
|
+
synthetic media verifying rows-then-export parity, row-level corrections,
|
|
71
|
+
and DB-first panel reads.
|
|
72
|
+
|
|
5
73
|
## What's New in v2.40.0
|
|
6
74
|
|
|
7
75
|
Control panel UX overhaul + docs refresh, from a full live audit of every
|
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
|
@@ -491,8 +491,45 @@ Key actions: `capabilities`, `install_guidance`, `resolve_output_root`, `plan`,
|
|
|
491
491
|
`publish_clip_metadata`, `commit_vision`, `summarize`, `get_report`,
|
|
492
492
|
`build_index`, `index_status`, `query_index`, `start_batch_job`,
|
|
493
493
|
`run_batch_job_slice`, `batch_job_status`, `list_batch_jobs`,
|
|
494
|
-
`cancel_batch_job`, `resume_batch_job`, `review_timeline_markers`,
|
|
495
|
-
`cleanup_artifacts
|
|
494
|
+
`cancel_batch_job`, `resume_batch_job`, `review_timeline_markers`,
|
|
495
|
+
`cleanup_artifacts`, `db_status`, `db_ingest`, `get_panel_state`,
|
|
496
|
+
`set_panel_state`, `session_start_context`, `update_clip_field`,
|
|
497
|
+
`update_shot_field`, `get_field_history`, `revert_field`,
|
|
498
|
+
`list_corrections`, `deepen`, `commit_shot_vision`, and
|
|
499
|
+
`vision_pending_sweep`.
|
|
500
|
+
|
|
501
|
+
**Deep shot-level vision tier (v2.42.0+).** Opt-in, estimate-first. Two
|
|
502
|
+
entry points share one per-shot schema (Visual / Content / Production /
|
|
503
|
+
Editorial / Cuttability / description / confidence):
|
|
504
|
+
- `depth="deep"` on any analyze action extends the deferred host-vision
|
|
505
|
+
payload with `deep_shot_schema`; each `shot_descriptions` entry must carry
|
|
506
|
+
the field groups. The first deep run returns `confirmation_required` with
|
|
507
|
+
a token-cost estimate — re-call with `confirm_deep=true`. Caps still apply.
|
|
508
|
+
- `deepen(clip_id|clip_dir, shot_index?|shot_indices?)` runs the pass
|
|
509
|
+
post-hoc on an already-analyzed clip. First call returns the estimate +
|
|
510
|
+
`confirm_token`; re-call with the token to get the deferred payload, read
|
|
511
|
+
its `frame_paths`, and commit via
|
|
512
|
+
`commit_shot_vision(clip_id, shots=[{shot_index, ...groups...}],
|
|
513
|
+
vision_token)`. Deep fields land as `vision_deep_v1` provenance rows;
|
|
514
|
+
human corrections always survive. Shots with no sampled frames on disk get
|
|
515
|
+
1–2 frames re-extracted via ffmpeg (read-only on source media).
|
|
516
|
+
`vision_pending_sweep(expire?, max_age_days?, reoffer?)` lists clips stuck
|
|
517
|
+
in `pending_host_analysis`; `reoffer=true` returns each clip's stored
|
|
518
|
+
deferred payload to finish the run, `expire=true` stamps them
|
|
519
|
+
`expired_host_analysis` so pendings never linger silently.
|
|
520
|
+
|
|
521
|
+
**DB-canonical analysis store (v2.41.0+).** The per-project SQLite DB
|
|
522
|
+
(`_soul/timeline_brain.sqlite`, schema v9+) is the source of truth for clip
|
|
523
|
+
analysis; `analysis.json` is a derived export written in lockstep. Analysis
|
|
524
|
+
runs write rows first (clips, shots, per-field subjective provenance,
|
|
525
|
+
transcript segments, sampled frames, QC observations) and then export the
|
|
526
|
+
JSON. Human corrections recorded via `update_clip_field` / `update_shot_field`
|
|
527
|
+
live as row-level provenance and always survive re-analysis. Readers
|
|
528
|
+
(panel API, exports) load DB-first and fall back to `analysis.json` for
|
|
529
|
+
reports that predate v9. `db_status` reports schema version + row counts;
|
|
530
|
+
`db_ingest` migrates an existing project's JSON reports (and
|
|
531
|
+
`corrections.json` sidecars) into the DB — run it once on older analysis
|
|
532
|
+
roots.
|
|
496
533
|
The tool never installs
|
|
497
534
|
dependencies and validates that outputs stay under
|
|
498
535
|
`davinci-resolve-mcp-analysis` project roots rather than beside source media.
|
|
@@ -86,7 +86,10 @@ transcripts once the search index is built.
|
|
|
86
86
|
|
|
87
87
|
The clip view shows the full summary, tags, star rating, editorial notes, and
|
|
88
88
|
a contact sheet of every detected shot. From here you can open the clip in
|
|
89
|
-
Resolve, jump to the transcript, or click into any shot.
|
|
89
|
+
Resolve, jump to the transcript, or click into any shot. `Deepen analysis`
|
|
90
|
+
copies a ready-made chat prompt that asks your MCP session to run the opt-in
|
|
91
|
+
deep shot pass (cost estimate first, then per-shot Visual / Content /
|
|
92
|
+
Editorial / Cuttability fields).
|
|
90
93
|
|
|
91
94
|
### Transcript
|
|
92
95
|
|
|
@@ -107,7 +110,9 @@ start", "After cut", "Flash frame", "Motion peak"). Subjective fields are
|
|
|
107
110
|
editable inline; edits are kept with the clip's analysis and merged on top of
|
|
108
111
|
future re-analysis so human notes survive fresh vision runs. `Open in Resolve`
|
|
109
112
|
jumps straight to the clip in the source viewer with the shot's mark in/out
|
|
110
|
-
set.
|
|
113
|
+
set. The field groups (Visual, Content, Production, Editorial, Cuttability)
|
|
114
|
+
are filled by the opt-in deep vision pass — `Deepen this shot` copies a chat
|
|
115
|
+
prompt that runs it for just this shot, estimate first.
|
|
111
116
|
|
|
112
117
|
### Media → History
|
|
113
118
|
|
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.42.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
|
@@ -8390,7 +8390,7 @@ HTML = r"""<!doctype html>
|
|
|
8390
8390
|
<span class="meta">${formatDuration(card.duration_seconds)} · ${shots.length} shots</span>
|
|
8391
8391
|
${cls.primary_use ? `<span class="review-chip">${escapeHtml(cls.primary_use)}</span>` : ''}
|
|
8392
8392
|
${cls.select_potential ? `<span class="review-chip ${selectChipClass(cls.select_potential)}">${escapeHtml(cls.select_potential)}</span>` : ''}
|
|
8393
|
-
<div class="actions"><button class="secondary" id="reviewClipTranscriptBtn">Transcript</button><button class="secondary" id="reviewClipOpenInResolveBtn">Open in Resolve</button></div>
|
|
8393
|
+
<div class="actions"><button class="secondary" id="reviewClipTranscriptBtn">Transcript</button><button class="secondary" id="reviewClipOpenInResolveBtn">Open in Resolve</button><button class="secondary" data-copy-chat-prompt="${escapeHtml(`Deepen the analysis of clip “${card.clip_name || state.review.currentClipId}”: call media_analysis(action="deepen", params={"clip_id": "${card.clip_id || state.review.currentClipId}"}), show me the cost estimate, and after I confirm, read the frames and commit the per-shot fields via commit_shot_vision.`)}">Deepen analysis</button></div>
|
|
8394
8394
|
`;
|
|
8395
8395
|
$('reviewClipSummary').textContent = data.clip_summary || data.clip_summary_oneliner || '';
|
|
8396
8396
|
$('reviewClipTags').innerHTML = tags.map(t => `<span class="review-chip">${escapeHtml(t)}</span>`).join('');
|
|
@@ -9440,6 +9440,7 @@ HTML = r"""<!doctype html>
|
|
|
9440
9440
|
<span class="meta">${escapeHtml(shot.description || '')}</span>
|
|
9441
9441
|
<div class="actions">
|
|
9442
9442
|
<button class="secondary" id="reviewShotOpenInResolveBtn">Open in Resolve</button>
|
|
9443
|
+
<button class="secondary" data-copy-chat-prompt="${escapeHtml(`Deepen shot ${shotIndex} of clip “${(state.review.currentClipData && state.review.currentClipData.card && state.review.currentClipData.card.clip_name) || clipId}”: call media_analysis(action="deepen", params={"clip_id": "${clipId}", "shot_index": ${shotIndex}}), show me the cost estimate, and after I confirm, read the frames and commit the per-shot fields via commit_shot_vision.`)}">Deepen this shot</button>
|
|
9443
9444
|
<button id="reviewShotEditToggleBtn" ${editing ? '' : 'class="secondary"'}>${editToggleLabel}</button>
|
|
9444
9445
|
</div>
|
|
9445
9446
|
`;
|
|
@@ -12450,6 +12451,25 @@ def _v2_load_analysis(clip_dir: str) -> Optional[Dict[str, Any]]:
|
|
|
12450
12451
|
return None
|
|
12451
12452
|
|
|
12452
12453
|
|
|
12454
|
+
def _v2_load_analysis_db_first(project_root: str, clip_dir: str) -> Optional[Dict[str, Any]]:
|
|
12455
|
+
"""C1 — DB-canonical reader with JSON fallback.
|
|
12456
|
+
|
|
12457
|
+
Falls back to analysis.json for reports that predate schema v9 and for
|
|
12458
|
+
job-linked report dirs whose rows live under another project's DB.
|
|
12459
|
+
"""
|
|
12460
|
+
try:
|
|
12461
|
+
from src.utils import analysis_store
|
|
12462
|
+
|
|
12463
|
+
report = analysis_store.load_db_report(
|
|
12464
|
+
project_root, clip_dir=os.path.basename(clip_dir.rstrip("/\\"))
|
|
12465
|
+
)
|
|
12466
|
+
if report is not None:
|
|
12467
|
+
return report
|
|
12468
|
+
except Exception:
|
|
12469
|
+
pass
|
|
12470
|
+
return _v2_load_analysis(clip_dir)
|
|
12471
|
+
|
|
12472
|
+
|
|
12453
12473
|
def _v2_clip_duration(report: Dict[str, Any]) -> Optional[float]:
|
|
12454
12474
|
marker_plan = report.get("clip_analysis_markers") if isinstance(report.get("clip_analysis_markers"), dict) else {}
|
|
12455
12475
|
duration = marker_plan.get("duration_seconds")
|
|
@@ -12530,7 +12550,7 @@ def list_analyzed_clips(project_root: str) -> Dict[str, Any]:
|
|
|
12530
12550
|
"""List analyzed clips for the bin grid. One row per analysis.json found."""
|
|
12531
12551
|
rows: List[Dict[str, Any]] = []
|
|
12532
12552
|
for slug, clip_dir in _v2_iter_analysis_dirs(project_root):
|
|
12533
|
-
report =
|
|
12553
|
+
report = _v2_load_analysis_db_first(project_root, clip_dir)
|
|
12534
12554
|
if report is None:
|
|
12535
12555
|
continue
|
|
12536
12556
|
rows.append(_v2_clip_summary_card(slug, clip_dir, report))
|
|
@@ -12561,7 +12581,7 @@ def get_analyzed_clip(project_root: str, clip_id: str) -> Dict[str, Any]:
|
|
|
12561
12581
|
clip_dir = _v2_find_clip_dir(project_root, clip_id)
|
|
12562
12582
|
if not clip_dir:
|
|
12563
12583
|
return {"success": False, "error": f"No analyzed clip found for id={clip_id}"}
|
|
12564
|
-
report =
|
|
12584
|
+
report = _v2_load_analysis_db_first(project_root, clip_dir)
|
|
12565
12585
|
if report is None:
|
|
12566
12586
|
return {"success": False, "error": "analysis.json unreadable"}
|
|
12567
12587
|
visual = report.get("visual") if isinstance(report.get("visual"), dict) else {}
|
|
@@ -12598,7 +12618,7 @@ def get_analyzed_clip_shots(project_root: str, clip_id: str) -> Dict[str, Any]:
|
|
|
12598
12618
|
clip_dir = _v2_find_clip_dir(project_root, clip_id)
|
|
12599
12619
|
if not clip_dir:
|
|
12600
12620
|
return {"success": False, "error": f"No analyzed clip found for id={clip_id}"}
|
|
12601
|
-
report =
|
|
12621
|
+
report = _v2_load_analysis_db_first(project_root, clip_dir)
|
|
12602
12622
|
if report is None:
|
|
12603
12623
|
return {"success": False, "error": "analysis.json unreadable"}
|
|
12604
12624
|
visual = report.get("visual") if isinstance(report.get("visual"), dict) else {}
|
|
@@ -12615,7 +12635,7 @@ def get_analyzed_clip_shot(project_root: str, clip_id: str, shot_index: int) ->
|
|
|
12615
12635
|
clip_dir = _v2_find_clip_dir(project_root, clip_id)
|
|
12616
12636
|
if not clip_dir:
|
|
12617
12637
|
return {"success": False, "error": f"No analyzed clip found for id={clip_id}"}
|
|
12618
|
-
report =
|
|
12638
|
+
report = _v2_load_analysis_db_first(project_root, clip_dir)
|
|
12619
12639
|
if report is None:
|
|
12620
12640
|
return {"success": False, "error": "analysis.json unreadable"}
|
|
12621
12641
|
visual = report.get("visual") if isinstance(report.get("visual"), dict) else {}
|
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.42.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.42.0"
|
|
15
15
|
|
|
16
16
|
import base64
|
|
17
17
|
import os
|
|
@@ -11215,6 +11215,25 @@ def _v2_update_field(project_root: str, p: Dict[str, Any], *, entity_type: str)
|
|
|
11215
11215
|
write_result = _v2_write_corrections(path, data)
|
|
11216
11216
|
if not write_result.get("success"):
|
|
11217
11217
|
return write_result
|
|
11218
|
+
# C1 — mirror the correction into the DB-canonical store (row-level
|
|
11219
|
+
# provenance). Best-effort: the sidecar remains authoritative for projects
|
|
11220
|
+
# whose DB predates v9 or lacks ingested rows.
|
|
11221
|
+
db_result: Dict[str, Any]
|
|
11222
|
+
try:
|
|
11223
|
+
from src.utils import analysis_store
|
|
11224
|
+
db_result = analysis_store.record_human_correction(
|
|
11225
|
+
project_root,
|
|
11226
|
+
clip_ref=clip_id or os.path.basename(os.path.dirname(path)),
|
|
11227
|
+
entity_type=entity_type,
|
|
11228
|
+
entity_uuid=entity_uuid,
|
|
11229
|
+
field_path=field_path,
|
|
11230
|
+
value=new_value,
|
|
11231
|
+
author=author,
|
|
11232
|
+
reason=reason,
|
|
11233
|
+
confidence=str(confidence) if confidence else None,
|
|
11234
|
+
)
|
|
11235
|
+
except Exception as exc: # noqa: BLE001 — DB mirror must not break corrections
|
|
11236
|
+
db_result = {"success": False, "error": f"{type(exc).__name__}: {exc}"}
|
|
11218
11237
|
return {
|
|
11219
11238
|
"success": True,
|
|
11220
11239
|
"entity_type": entity_type,
|
|
@@ -11225,6 +11244,7 @@ def _v2_update_field(project_root: str, p: Dict[str, Any], *, entity_type: str)
|
|
|
11225
11244
|
"author": author,
|
|
11226
11245
|
"timestamp": now,
|
|
11227
11246
|
"corrections_path": path,
|
|
11247
|
+
"db": db_result,
|
|
11228
11248
|
}
|
|
11229
11249
|
|
|
11230
11250
|
|
|
@@ -11307,6 +11327,35 @@ def _v2_revert_field(project_root: str, p: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
11307
11327
|
write_result = _v2_write_corrections(path, data)
|
|
11308
11328
|
if not write_result.get("success"):
|
|
11309
11329
|
return write_result
|
|
11330
|
+
# C1 — mirror the revert into the DB-canonical store. A revert back to a
|
|
11331
|
+
# human value re-records it; a revert to machine-derived clears the human
|
|
11332
|
+
# row so the export overlay falls back to the blob value.
|
|
11333
|
+
db_result: Dict[str, Any]
|
|
11334
|
+
try:
|
|
11335
|
+
from src.utils import analysis_store
|
|
11336
|
+
clip_ref = clip_id or os.path.basename(os.path.dirname(path))
|
|
11337
|
+
if action_taken == "removed (back to machine-derived)" or revert_source != "human":
|
|
11338
|
+
db_result = analysis_store.clear_human_field(
|
|
11339
|
+
project_root,
|
|
11340
|
+
clip_ref=clip_ref,
|
|
11341
|
+
entity_type=entity_type,
|
|
11342
|
+
entity_uuid=entity_uuid,
|
|
11343
|
+
field_path=field_path,
|
|
11344
|
+
author=author,
|
|
11345
|
+
)
|
|
11346
|
+
else:
|
|
11347
|
+
db_result = analysis_store.record_human_correction(
|
|
11348
|
+
project_root,
|
|
11349
|
+
clip_ref=clip_ref,
|
|
11350
|
+
entity_type=entity_type,
|
|
11351
|
+
entity_uuid=entity_uuid,
|
|
11352
|
+
field_path=field_path,
|
|
11353
|
+
value=revert_to,
|
|
11354
|
+
author=author,
|
|
11355
|
+
reason=f"revert by {author}",
|
|
11356
|
+
)
|
|
11357
|
+
except Exception as exc: # noqa: BLE001 — DB mirror must not break corrections
|
|
11358
|
+
db_result = {"success": False, "error": f"{type(exc).__name__}: {exc}"}
|
|
11310
11359
|
return {
|
|
11311
11360
|
"success": True,
|
|
11312
11361
|
"action": action_taken,
|
|
@@ -11314,6 +11363,7 @@ def _v2_revert_field(project_root: str, p: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
11314
11363
|
"reverted_value": revert_to,
|
|
11315
11364
|
"timestamp": now,
|
|
11316
11365
|
"corrections_path": path,
|
|
11366
|
+
"db": db_result,
|
|
11317
11367
|
}
|
|
11318
11368
|
|
|
11319
11369
|
|
|
@@ -14955,6 +15005,25 @@ async def media_analysis(action: str, params: Optional[Dict[str, Any]] = None, c
|
|
|
14955
15005
|
"cancel_batch_job",
|
|
14956
15006
|
"resume_batch_job",
|
|
14957
15007
|
"cleanup_artifacts",
|
|
15008
|
+
# V2 session/panel state + C4 corrections. These checks lived inside
|
|
15009
|
+
# this block since v2.24.0 but were missing from the membership set,
|
|
15010
|
+
# making them unreachable from MCP dispatch (the panel proxied to the
|
|
15011
|
+
# helpers directly, which masked it). Fixed as part of C1 (Phase A).
|
|
15012
|
+
"get_panel_state",
|
|
15013
|
+
"set_panel_state",
|
|
15014
|
+
"session_start_context",
|
|
15015
|
+
"update_shot_field",
|
|
15016
|
+
"update_clip_field",
|
|
15017
|
+
"get_field_history",
|
|
15018
|
+
"revert_field",
|
|
15019
|
+
"list_corrections",
|
|
15020
|
+
# C1 — DB-canonical analysis store.
|
|
15021
|
+
"db_status",
|
|
15022
|
+
"db_ingest",
|
|
15023
|
+
# Phase B — deep shot-level vision tier.
|
|
15024
|
+
"deepen",
|
|
15025
|
+
"commit_shot_vision",
|
|
15026
|
+
"vision_pending_sweep",
|
|
14958
15027
|
}:
|
|
14959
15028
|
root = resolve_media_analysis_output_root(
|
|
14960
15029
|
project_name=project_name,
|
|
@@ -15007,6 +15076,49 @@ async def media_analysis(action: str, params: Optional[Dict[str, Any]] = None, c
|
|
|
15007
15076
|
return _v2_revert_field(project_root, p)
|
|
15008
15077
|
if action == "list_corrections":
|
|
15009
15078
|
return _v2_list_corrections(project_root, p)
|
|
15079
|
+
# C1 — DB-canonical analysis store (Phase A).
|
|
15080
|
+
if action == "db_status":
|
|
15081
|
+
from src.utils import analysis_store
|
|
15082
|
+
return analysis_store.db_status(project_root)
|
|
15083
|
+
if action == "db_ingest":
|
|
15084
|
+
from src.utils import analysis_store
|
|
15085
|
+
return analysis_store.ingest_project(project_root)
|
|
15086
|
+
# Phase B — deep shot-level vision tier.
|
|
15087
|
+
if action == "deepen":
|
|
15088
|
+
from src.utils import deep_vision
|
|
15089
|
+
clip_ref = p.get("clip_id") or p.get("clipId") or p.get("clip_dir") or p.get("clipDir") or p.get("file_path") or p.get("filePath")
|
|
15090
|
+
if not clip_ref:
|
|
15091
|
+
return _err("deepen requires clip_id, clip_dir, or file_path")
|
|
15092
|
+
raw_indices = p.get("shot_indices") or p.get("shotIndices")
|
|
15093
|
+
if raw_indices is None and p.get("shot_index") is not None:
|
|
15094
|
+
raw_indices = [p.get("shot_index")]
|
|
15095
|
+
return deep_vision.deepen_clip(
|
|
15096
|
+
project_root,
|
|
15097
|
+
clip_ref=clip_ref,
|
|
15098
|
+
shot_indices=[int(i) for i in raw_indices] if raw_indices else None,
|
|
15099
|
+
confirm_token=p.get("confirm_token") or p.get("confirmToken"),
|
|
15100
|
+
job_id=p.get("job_id") or p.get("jobId"),
|
|
15101
|
+
)
|
|
15102
|
+
if action == "commit_shot_vision":
|
|
15103
|
+
from src.utils import deep_vision
|
|
15104
|
+
clip_ref = p.get("clip_id") or p.get("clipId") or p.get("clip_dir") or p.get("clipDir") or p.get("file_path") or p.get("filePath")
|
|
15105
|
+
if not clip_ref:
|
|
15106
|
+
return _err("commit_shot_vision requires clip_id, clip_dir, or file_path")
|
|
15107
|
+
return deep_vision.commit_shot_vision(
|
|
15108
|
+
project_root,
|
|
15109
|
+
shots=p.get("shots"),
|
|
15110
|
+
vision_token=p.get("vision_token") or p.get("visionToken"),
|
|
15111
|
+
clip_ref=clip_ref,
|
|
15112
|
+
author=p.get("author") or "host_chat",
|
|
15113
|
+
)
|
|
15114
|
+
if action == "vision_pending_sweep":
|
|
15115
|
+
from src.utils import deep_vision
|
|
15116
|
+
return deep_vision.vision_pending_sweep(
|
|
15117
|
+
project_root,
|
|
15118
|
+
expire=_media_analysis_bool(p.get("expire"), False),
|
|
15119
|
+
max_age_days=p.get("max_age_days") or p.get("maxAgeDays"),
|
|
15120
|
+
reoffer=_media_analysis_bool(p.get("reoffer"), False),
|
|
15121
|
+
)
|
|
15010
15122
|
if action in {"build_index", "rebuild_index"}:
|
|
15011
15123
|
return build_analysis_index(project_root, index_path=p.get("index_path") or p.get("indexPath"))
|
|
15012
15124
|
if action == "index_status":
|
|
@@ -15408,6 +15520,26 @@ async def media_analysis(action: str, params: Optional[Dict[str, Any]] = None, c
|
|
|
15408
15520
|
"cancel_batch_job",
|
|
15409
15521
|
"resume_batch_job",
|
|
15410
15522
|
"cleanup_artifacts",
|
|
15523
|
+
"get_panel_state",
|
|
15524
|
+
"set_panel_state",
|
|
15525
|
+
"session_start_context",
|
|
15526
|
+
"update_shot_field",
|
|
15527
|
+
"update_clip_field",
|
|
15528
|
+
"get_field_history",
|
|
15529
|
+
"revert_field",
|
|
15530
|
+
"list_corrections",
|
|
15531
|
+
"db_status",
|
|
15532
|
+
"db_ingest",
|
|
15533
|
+
"deepen",
|
|
15534
|
+
"commit_shot_vision",
|
|
15535
|
+
"vision_pending_sweep",
|
|
15536
|
+
"get_caps",
|
|
15537
|
+
"set_caps_preset",
|
|
15538
|
+
"get_usage",
|
|
15539
|
+
"coverage_report",
|
|
15540
|
+
"get_resolve_ai_usage",
|
|
15541
|
+
"get_ai_governance",
|
|
15542
|
+
"set_ai_governance",
|
|
15411
15543
|
])
|
|
15412
15544
|
|
|
15413
15545
|
|