davinci-resolve-mcp 2.64.0 → 2.65.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 +42 -0
- package/README.md +1 -1
- package/docs/guides/media-analysis-guide.md +17 -0
- package/install.py +1 -1
- package/package.json +1 -1
- package/src/analysis_dashboard.py +154 -17
- package/src/control_panel_i18n.py +780 -0
- package/src/granular/common.py +1 -1
- package/src/server.py +1 -1
- package/src/utils/media_analysis.py +146 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,48 @@
|
|
|
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.65.0
|
|
6
|
+
|
|
7
|
+
Bundles two community contributions from @double2tea: an optional HTTP
|
|
8
|
+
transcription backend and a localized, build-aware control panel. Both are
|
|
9
|
+
opt-in and change no default behavior — with neither configured, existing
|
|
10
|
+
transcription backends and the English panel work exactly as before.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **MLX Audio Router transcription backend** (PR #95, @double2tea) — an optional
|
|
15
|
+
HTTP transcription backend that runs outside the Resolve MCP Python
|
|
16
|
+
environment. Enabled only when `DAVINCI_RESOLVE_MCP_MLX_AUDIO_URL` is set and a
|
|
17
|
+
bounded `GET /health` probe succeeds; an optional
|
|
18
|
+
`DAVINCI_RESOLVE_MCP_MLX_AUDIO_MODEL` overrides the router's default model.
|
|
19
|
+
When configured and healthy it is preferred in transcription capability
|
|
20
|
+
ordering, and its response is normalized into the existing JSON/SRT/VTT
|
|
21
|
+
transcript artifacts. Standard-library only (`urllib`); the MCP server never
|
|
22
|
+
installs, starts, or downloads anything on the router's behalf, and existing
|
|
23
|
+
Whisper backends are untouched when no URL is set.
|
|
24
|
+
- **Control panel localization (English / Simplified Chinese)** (PR #96,
|
|
25
|
+
@double2tea) — a persistent language switch on the local control panel.
|
|
26
|
+
English remains the authored, canonical UI; the browser stores only the
|
|
27
|
+
selected locale in `localStorage` and the initial locale follows browser
|
|
28
|
+
preferences. Translation runs client-side over the DOM (text nodes plus
|
|
29
|
+
`aria-label`/`title`/`placeholder`) and is fully reversible; no server API or
|
|
30
|
+
persisted project data changes.
|
|
31
|
+
- **Build-aware AI console** (PR #96, @double2tea) — the Resolve AI console now
|
|
32
|
+
uses the runtime capability payload to disable actions the connected Resolve
|
|
33
|
+
build cannot execute, distinguishing "Requires Resolve 21+" from "Unavailable
|
|
34
|
+
on this build" and surfacing the Resolve 20 transcription methods
|
|
35
|
+
(`TranscribeAudio` / `ClearTranscription`). Narrow-screen navigation and the
|
|
36
|
+
mobile footer were also improved.
|
|
37
|
+
|
|
38
|
+
### Validation
|
|
39
|
+
|
|
40
|
+
- Static checks and drift guards pass; `tests/test_media_analysis.py` (110),
|
|
41
|
+
`tests/test_control_panel_i18n.py` + `tests/test_control_panel_ai_capabilities.py`
|
|
42
|
+
+ `tests/test_open_control_panel.py` (14) pass on the merged tree.
|
|
43
|
+
- No DaVinci Resolve scripting behavior changed: PR #95 is a self-contained
|
|
44
|
+
HTTP/stdlib backend gated behind an env var, and PR #96 changes only the
|
|
45
|
+
control panel's client-side HTML/JS. Live Resolve validation not required.
|
|
46
|
+
|
|
5
47
|
## What's New in v2.64.0
|
|
6
48
|
|
|
7
49
|
Adds opt-in support for DaVinci Resolve's **Network** external-scripting mode
|
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)
|
|
@@ -79,6 +79,23 @@ which ffmpeg && ffmpeg -version 2>&1 | head -1
|
|
|
79
79
|
which whisper 2>/dev/null || which whisper-cpp 2>/dev/null || python3 -c "import whisper" 2>/dev/null
|
|
80
80
|
```
|
|
81
81
|
|
|
82
|
+
An HTTP MLX Audio Router can be used without installing a transcription module
|
|
83
|
+
into the Resolve MCP Python environment:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
export DAVINCI_RESOLVE_MCP_MLX_AUDIO_URL=http://127.0.0.1:8000
|
|
87
|
+
# Optional: omit this to use the router's own default model.
|
|
88
|
+
export DAVINCI_RESOLVE_MCP_MLX_AUDIO_MODEL=mlx-community/Qwen3-ASR-1.7B-8bit
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The configured service must expose `GET /health`, returning JSON with
|
|
92
|
+
`{"status":"ok"}`, and `POST /stt`. The transcription request uses the local
|
|
93
|
+
source path and requests JSON output; the response must contain a `transcript`
|
|
94
|
+
string whose value is a JSON object with `text` and `segments`. The
|
|
95
|
+
[Audiobox MLX Audio Router](https://github.com/double2tea/Audiobox) implements
|
|
96
|
+
this contract. Model download behavior follows the existing
|
|
97
|
+
`allow_model_download` transcription option.
|
|
98
|
+
|
|
82
99
|
FFprobe is required. If missing:
|
|
83
100
|
- macOS: `brew install ffmpeg`
|
|
84
101
|
- Linux: `sudo apt install ffmpeg` or `sudo dnf install ffmpeg`
|
package/install.py
CHANGED
|
@@ -36,7 +36,7 @@ from src.utils.update_check import (
|
|
|
36
36
|
|
|
37
37
|
# ─── Version ──────────────────────────────────────────────────────────────────
|
|
38
38
|
|
|
39
|
-
VERSION = "2.
|
|
39
|
+
VERSION = "2.65.0"
|
|
40
40
|
# Only hard floor: mcp[cli] requires Python 3.10+. There is no upper bound —
|
|
41
41
|
# Resolve's scripting bridge loads into newer interpreters on recent builds
|
|
42
42
|
# (Python 3.14 verified against Resolve Studio 20.3.2). Older Resolve builds
|
package/package.json
CHANGED
|
@@ -49,6 +49,7 @@ from src.utils.analysis_memory import read_panel_state, write_panel_state
|
|
|
49
49
|
from src.utils import brain_edits as _brain_edits
|
|
50
50
|
from src.utils import timeline_versioning as _timeline_versioning
|
|
51
51
|
from src.utils import timeline_brain_db as _timeline_brain_db
|
|
52
|
+
from src.control_panel_i18n import localization_script
|
|
52
53
|
|
|
53
54
|
|
|
54
55
|
HTML = r"""<!doctype html>
|
|
@@ -616,6 +617,33 @@ HTML = r"""<!doctype html>
|
|
|
616
617
|
gap: var(--space-2);
|
|
617
618
|
min-width: 0;
|
|
618
619
|
}
|
|
620
|
+
.language-switch {
|
|
621
|
+
display: inline-flex;
|
|
622
|
+
align-items: center;
|
|
623
|
+
min-height: 32px;
|
|
624
|
+
padding: 2px;
|
|
625
|
+
border: 1px solid var(--border-default);
|
|
626
|
+
border-radius: var(--radius-sm);
|
|
627
|
+
background: var(--bg-elevated-1);
|
|
628
|
+
}
|
|
629
|
+
.language-switch button {
|
|
630
|
+
min-height: 26px;
|
|
631
|
+
padding: 0 8px;
|
|
632
|
+
border: 0;
|
|
633
|
+
border-radius: 2px;
|
|
634
|
+
background: transparent;
|
|
635
|
+
color: var(--text-tertiary);
|
|
636
|
+
font-size: 11px;
|
|
637
|
+
font-weight: 600;
|
|
638
|
+
}
|
|
639
|
+
.language-switch button:hover {
|
|
640
|
+
background: var(--bg-hover);
|
|
641
|
+
color: var(--text-primary);
|
|
642
|
+
}
|
|
643
|
+
.language-switch button.active {
|
|
644
|
+
background: var(--accent-brand-muted);
|
|
645
|
+
color: var(--accent-brand-hover);
|
|
646
|
+
}
|
|
619
647
|
.version-badge {
|
|
620
648
|
position: relative;
|
|
621
649
|
display: inline-flex;
|
|
@@ -2505,16 +2533,57 @@ HTML = r"""<!doctype html>
|
|
|
2505
2533
|
.ai-op-btn.danger { background: #b4452f; }
|
|
2506
2534
|
.ai-caps-grid {
|
|
2507
2535
|
display: grid;
|
|
2508
|
-
grid-template-columns: repeat(auto-
|
|
2536
|
+
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
2509
2537
|
gap: var(--space-2);
|
|
2510
2538
|
margin-top: var(--space-2);
|
|
2511
2539
|
}
|
|
2512
2540
|
.ai-caps-item {
|
|
2513
|
-
display:
|
|
2541
|
+
display: grid;
|
|
2542
|
+
grid-template-columns: 9px minmax(0, 1fr) auto;
|
|
2514
2543
|
align-items: center;
|
|
2515
|
-
gap: 8px;
|
|
2544
|
+
column-gap: 8px;
|
|
2545
|
+
row-gap: 2px;
|
|
2546
|
+
padding: var(--space-2) 0;
|
|
2547
|
+
border-bottom: 1px solid var(--border-subtle);
|
|
2516
2548
|
font-size: var(--text-xs);
|
|
2517
2549
|
}
|
|
2550
|
+
.ai-build-summary {
|
|
2551
|
+
display: flex;
|
|
2552
|
+
align-items: center;
|
|
2553
|
+
justify-content: space-between;
|
|
2554
|
+
gap: var(--space-3);
|
|
2555
|
+
padding: var(--space-3);
|
|
2556
|
+
border: 1px solid var(--border-default);
|
|
2557
|
+
border-radius: var(--radius-sm);
|
|
2558
|
+
background: var(--bg-elevated-1);
|
|
2559
|
+
margin-bottom: var(--space-3);
|
|
2560
|
+
}
|
|
2561
|
+
.ai-build-summary strong { color: var(--text-primary); }
|
|
2562
|
+
.ai-build-count { color: var(--text-secondary); font-size: var(--ops-text-label); }
|
|
2563
|
+
.ai-caps-extra { grid-column: 2 / -1; }
|
|
2564
|
+
.ai-caps-status {
|
|
2565
|
+
grid-column: 3;
|
|
2566
|
+
grid-row: 1;
|
|
2567
|
+
color: var(--text-tertiary);
|
|
2568
|
+
font-size: 10px;
|
|
2569
|
+
white-space: nowrap;
|
|
2570
|
+
}
|
|
2571
|
+
.ai-caps-status.available { color: var(--accent-success); }
|
|
2572
|
+
.ai-section-unavailable > :not(.caps-section-head),
|
|
2573
|
+
.ai-section-unavailable .caps-section-hint {
|
|
2574
|
+
display: none;
|
|
2575
|
+
}
|
|
2576
|
+
.ai-section-unavailable .caps-section-head {
|
|
2577
|
+
flex-direction: row;
|
|
2578
|
+
align-items: center;
|
|
2579
|
+
justify-content: space-between;
|
|
2580
|
+
margin-bottom: 0;
|
|
2581
|
+
}
|
|
2582
|
+
.ai-section-status {
|
|
2583
|
+
color: var(--accent-warning);
|
|
2584
|
+
font-size: 10px;
|
|
2585
|
+
font-weight: 600;
|
|
2586
|
+
}
|
|
2518
2587
|
.ai-caps-dot { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto; }
|
|
2519
2588
|
.ai-caps-dot.on { background: #34a853; }
|
|
2520
2589
|
.ai-caps-dot.off { background: rgba(255,255,255,0.25); }
|
|
@@ -3903,7 +3972,19 @@ HTML = r"""<!doctype html>
|
|
|
3903
3972
|
.settings-grid { grid-template-columns: 1fr; }
|
|
3904
3973
|
}
|
|
3905
3974
|
@media (max-width: 620px) {
|
|
3975
|
+
main { padding-bottom: var(--space-3); }
|
|
3976
|
+
.lab-footer {
|
|
3977
|
+
position: static;
|
|
3978
|
+
height: auto;
|
|
3979
|
+
min-height: 112px;
|
|
3980
|
+
}
|
|
3906
3981
|
.overview-grid { grid-template-columns: 1fr; }
|
|
3982
|
+
.nav-left { flex: 0 0 auto; }
|
|
3983
|
+
.wordmark span:first-child { display: none; }
|
|
3984
|
+
.nav-links > .github-icon-link { display: none; }
|
|
3985
|
+
.version-badge .version-label { display: none; }
|
|
3986
|
+
.version-badge { padding: 0 8px; }
|
|
3987
|
+
.language-switch button { padding: 0 6px; }
|
|
3907
3988
|
}
|
|
3908
3989
|
</style>
|
|
3909
3990
|
</head>
|
|
@@ -3969,6 +4050,10 @@ HTML = r"""<!doctype html>
|
|
|
3969
4050
|
</div>
|
|
3970
4051
|
</nav>
|
|
3971
4052
|
<div class="nav-links">
|
|
4053
|
+
<div class="language-switch" role="group" aria-label="Interface language">
|
|
4054
|
+
<button type="button" data-locale="zh-CN" aria-pressed="false">中文</button>
|
|
4055
|
+
<button type="button" data-locale="en" aria-pressed="false">EN</button>
|
|
4056
|
+
</div>
|
|
3972
4057
|
<button id="versionBadge" class="version-badge" type="button" aria-label="MCP version and updates" title="MCP version">
|
|
3973
4058
|
<span class="version-label">MCP</span>
|
|
3974
4059
|
<span class="version-number" id="versionNumber">…</span>
|
|
@@ -4296,7 +4381,7 @@ HTML = r"""<!doctype html>
|
|
|
4296
4381
|
<section class="span-12">
|
|
4297
4382
|
<div class="section-top">
|
|
4298
4383
|
<div>
|
|
4299
|
-
<h2>Resolve
|
|
4384
|
+
<h2>Resolve AI Console</h2>
|
|
4300
4385
|
<p class="section-sub">Run Resolve's local AI operations on the current Media Pool folder or a specific clip. These run on Resolve's GPU/AI engine — the analysis and slate ops are safe and reversible; <strong>motion-deblur</strong> and <strong>speech generation</strong> create new media files and ask for confirmation first. Source media is never modified. Every run is recorded in the <em>Resolve 21 AI ops</em> ledger (Preferences → Caps + Safety).</p>
|
|
4301
4386
|
</div>
|
|
4302
4387
|
</div>
|
|
@@ -4305,8 +4390,8 @@ HTML = r"""<!doctype html>
|
|
|
4305
4390
|
<div class="caps-section-hint">Checking which AI methods this Resolve build exposes…</div>
|
|
4306
4391
|
</div>
|
|
4307
4392
|
|
|
4308
|
-
<div class="caps-section" style="margin-top:12px;">
|
|
4309
|
-
<div class="caps-section-head"><div class="caps-section-title">Governance
|
|
4393
|
+
<div class="caps-section" id="aiGovernanceSection" style="margin-top:12px;">
|
|
4394
|
+
<div class="caps-section-head"><div class="caps-section-title">Governance <span class="ai-section-status"></span></div>
|
|
4310
4395
|
<div class="caps-section-hint">Per-session limits for the two media-creating ops (deblur, speech). In <strong>Advisory</strong> mode you're warned in the confirm dialog but never blocked; in <strong>Enforce</strong> mode an over-tier run is refused until you raise the tier, relax the mode, or consciously override. Pick the tier that matches the job.</div></div>
|
|
4311
4396
|
<div id="aiGovTiers" class="caps-preset-cards" role="radiogroup" aria-label="AI governance tier"></div>
|
|
4312
4397
|
<div class="review-view-toggle" id="aiGovMode" role="radiogroup" aria-label="Governance mode" style="margin-top:10px;">
|
|
@@ -4349,8 +4434,8 @@ HTML = r"""<!doctype html>
|
|
|
4349
4434
|
</div>
|
|
4350
4435
|
</div>
|
|
4351
4436
|
|
|
4352
|
-
<div class="caps-section" style="margin-top:12px;">
|
|
4353
|
-
<div class="caps-section-head"><div class="caps-section-title">Motion deblur
|
|
4437
|
+
<div class="caps-section" id="aiMotionDeblurSection" style="margin-top:12px;">
|
|
4438
|
+
<div class="caps-section-head"><div class="caps-section-title">Motion deblur <span class="ai-section-status"></span></div>
|
|
4354
4439
|
<div class="caps-section-hint">Renders new deblurred media. Creates files; asks for confirmation. Leave fields blank for Resolve defaults.</div></div>
|
|
4355
4440
|
<div class="settings-grid">
|
|
4356
4441
|
<label>Format <input id="aiDeblurFormat" type="text" placeholder="mov"></label>
|
|
@@ -4362,8 +4447,8 @@ HTML = r"""<!doctype html>
|
|
|
4362
4447
|
</div>
|
|
4363
4448
|
</div>
|
|
4364
4449
|
|
|
4365
|
-
<div class="caps-section" style="margin-top:12px;">
|
|
4366
|
-
<div class="caps-section-head"><div class="caps-section-title">Speech generator
|
|
4450
|
+
<div class="caps-section" id="aiSpeechSection" style="margin-top:12px;">
|
|
4451
|
+
<div class="caps-section-head"><div class="caps-section-title">Speech generator <span class="ai-section-status"></span></div>
|
|
4367
4452
|
<div class="caps-section-hint">AI text-to-speech. Requires the AI Speech Generator Extra. Creates a new audio item; asks for confirmation.</div></div>
|
|
4368
4453
|
<div class="settings-grid">
|
|
4369
4454
|
<label style="grid-column:1/-1;">Text <textarea id="aiSpeechText" rows="2" placeholder="Text to synthesize"></textarea></label>
|
|
@@ -4378,8 +4463,8 @@ HTML = r"""<!doctype html>
|
|
|
4378
4463
|
</div>
|
|
4379
4464
|
</div>
|
|
4380
4465
|
|
|
4381
|
-
<div class="caps-section" style="margin-top:12px;">
|
|
4382
|
-
<div class="caps-section-head"><div class="caps-section-title">Session
|
|
4466
|
+
<div class="caps-section" id="aiSessionSection" style="margin-top:12px;">
|
|
4467
|
+
<div class="caps-section-head"><div class="caps-section-title">Session <span class="ai-section-status"></span></div>
|
|
4383
4468
|
<div class="caps-section-hint">Quiet Resolve's background tasks for this session before heavy work. Resets on restart.</div></div>
|
|
4384
4469
|
<div class="ai-op-row"><button class="ai-op-btn ghost" data-ai-op="disable_background_tasks">Disable background tasks</button></div>
|
|
4385
4470
|
</div>
|
|
@@ -4960,6 +5045,8 @@ HTML = r"""<!doctype html>
|
|
|
4960
5045
|
</footer>
|
|
4961
5046
|
|
|
4962
5047
|
<script>
|
|
5048
|
+
/* CONTROL_PANEL_I18N */
|
|
5049
|
+
|
|
4963
5050
|
const state = {
|
|
4964
5051
|
boot: null,
|
|
4965
5052
|
projects: null,
|
|
@@ -5846,6 +5933,7 @@ HTML = r"""<!doctype html>
|
|
|
5846
5933
|
renderOverview();
|
|
5847
5934
|
renderDiagnostics();
|
|
5848
5935
|
renderProjects();
|
|
5936
|
+
if (_aiConsoleInit) renderAiConsole();
|
|
5849
5937
|
refreshRecentRootsDropdown();
|
|
5850
5938
|
}
|
|
5851
5939
|
|
|
@@ -7795,23 +7883,33 @@ HTML = r"""<!doctype html>
|
|
|
7795
7883
|
clear_audio_classification: 'clear_audio_classification',
|
|
7796
7884
|
analyze_for_intellisearch: 'analyze_for_intellisearch',
|
|
7797
7885
|
analyze_for_slate: 'analyze_for_slate',
|
|
7886
|
+
transcribe_audio: 'transcribe_audio',
|
|
7887
|
+
clear_transcription: 'clear_transcription',
|
|
7798
7888
|
remove_motion_blur: 'remove_motion_blur',
|
|
7799
7889
|
generate_speech: 'generate_speech',
|
|
7800
7890
|
disable_background_tasks: 'disable_background_tasks',
|
|
7801
7891
|
};
|
|
7892
|
+
const AI_OP_REQUIRES_21 = new Set([
|
|
7893
|
+
'perform_audio_classification', 'clear_audio_classification',
|
|
7894
|
+
'analyze_for_intellisearch', 'analyze_for_slate', 'remove_motion_blur',
|
|
7895
|
+
'generate_speech', 'disable_background_tasks',
|
|
7896
|
+
]);
|
|
7802
7897
|
let _aiConsoleInit = false;
|
|
7803
7898
|
|
|
7804
7899
|
function renderAiConsole() {
|
|
7805
7900
|
const feats = (state.boot?.resolve?.ai_features) || {};
|
|
7806
7901
|
const features = feats.features || {};
|
|
7807
7902
|
const requiresExtra = feats.requires_extra || {};
|
|
7903
|
+
const version = state.boot?.resolve?.version_string || 'unknown';
|
|
7808
7904
|
const capsEl = $('aiConsoleCaps');
|
|
7809
7905
|
if (capsEl) {
|
|
7810
7906
|
if (state.boot?.resolve?.available !== true) {
|
|
7811
7907
|
capsEl.innerHTML = '<div class="caps-section-hint">Resolve is not connected. Open a project in DaVinci Resolve, then reload.</div>';
|
|
7812
7908
|
} else {
|
|
7813
|
-
const
|
|
7814
|
-
|
|
7909
|
+
const ops = Object.keys(AI_OP_LABELS).filter(op => op in AI_OP_FEATURE);
|
|
7910
|
+
const availableCount = ops.filter(op => !!features[AI_OP_FEATURE[op]]).length;
|
|
7911
|
+
const items = ops
|
|
7912
|
+
.sort((a, b) => Number(!!features[AI_OP_FEATURE[b]]) - Number(!!features[AI_OP_FEATURE[a]]))
|
|
7815
7913
|
.map(op => {
|
|
7816
7914
|
const key = AI_OP_FEATURE[op];
|
|
7817
7915
|
const on = !!features[key];
|
|
@@ -7819,13 +7917,45 @@ HTML = r"""<!doctype html>
|
|
|
7819
7917
|
return `<div class="ai-caps-item"><span class="ai-caps-dot ${on ? 'on' : 'off'}"></span>`
|
|
7820
7918
|
+ `<span>${escapeHtml(AI_OP_LABELS[op])}</span>`
|
|
7821
7919
|
+ (extra ? `<span class="ai-caps-extra">· needs ${escapeHtml(extra)}</span>` : '')
|
|
7920
|
+
+ `<span class="ai-caps-status ${on ? 'available' : ''}">${on ? 'Available now' : (AI_OP_REQUIRES_21.has(op) ? 'Requires Resolve 21+' : 'Unavailable on this build')}</span>`
|
|
7822
7921
|
+ `</div>`;
|
|
7823
7922
|
}).join('');
|
|
7824
|
-
capsEl.innerHTML = `<div class="
|
|
7825
|
-
+ `<div class="caps-section-
|
|
7923
|
+
capsEl.innerHTML = `<div class="ai-build-summary"><strong>DaVinci Resolve ${escapeHtml(version)}</strong><span class="ai-build-count">${availableCount} of ${ops.length} AI console actions available now</span></div>`
|
|
7924
|
+
+ `<div class="caps-section-head"><div class="caps-section-title">Available on this Resolve build</div>`
|
|
7925
|
+
+ `<div class="caps-section-hint">Unavailable actions are disabled. Resolve 21 methods may also require the named Extra from Extras Download Manager.</div></div>`
|
|
7826
7926
|
+ `<div class="ai-caps-grid">${items}</div>`;
|
|
7827
7927
|
}
|
|
7828
7928
|
}
|
|
7929
|
+
document.querySelectorAll('#panel-aiconsole .ai-op-btn').forEach(btn => {
|
|
7930
|
+
const feature = AI_OP_FEATURE[btn.dataset.aiOp];
|
|
7931
|
+
const available = feature ? !!features[feature] : false;
|
|
7932
|
+
btn.disabled = !available;
|
|
7933
|
+
btn.title = available ? '' : (AI_OP_REQUIRES_21.has(btn.dataset.aiOp)
|
|
7934
|
+
? 'Requires DaVinci Resolve 21 or newer'
|
|
7935
|
+
: 'Unavailable on this Resolve build');
|
|
7936
|
+
});
|
|
7937
|
+
const speakerDetection = $('aiSpeakerDetection');
|
|
7938
|
+
if (speakerDetection) {
|
|
7939
|
+
const resolveMajor = Number(state.boot?.resolve?.version?.[0] || 0);
|
|
7940
|
+
speakerDetection.disabled = resolveMajor < 21;
|
|
7941
|
+
speakerDetection.title = resolveMajor < 21 ? 'Speaker detection requires DaVinci Resolve 21 or newer' : '';
|
|
7942
|
+
}
|
|
7943
|
+
const sectionAvailability = {
|
|
7944
|
+
aiGovernanceSection: !!features.remove_motion_blur || !!features.generate_speech,
|
|
7945
|
+
aiMotionDeblurSection: !!features.remove_motion_blur,
|
|
7946
|
+
aiSpeechSection: !!features.generate_speech,
|
|
7947
|
+
aiSessionSection: !!features.disable_background_tasks,
|
|
7948
|
+
};
|
|
7949
|
+
Object.entries(sectionAvailability).forEach(([id, available]) => {
|
|
7950
|
+
const section = $(id);
|
|
7951
|
+
if (!section) return;
|
|
7952
|
+
section.classList.toggle('ai-section-unavailable', !available);
|
|
7953
|
+
const status = section.querySelector('.ai-section-status');
|
|
7954
|
+
if (status) status.textContent = available ? '' : 'Requires Resolve 21+';
|
|
7955
|
+
section.querySelectorAll('input, textarea, select, button').forEach(control => {
|
|
7956
|
+
control.disabled = !available;
|
|
7957
|
+
});
|
|
7958
|
+
});
|
|
7829
7959
|
// Slate color dropdown (once).
|
|
7830
7960
|
const sel = $('aiSlateColor');
|
|
7831
7961
|
if (sel && !sel.options.length) {
|
|
@@ -7925,7 +8055,7 @@ HTML = r"""<!doctype html>
|
|
|
7925
8055
|
refreshResolveAiOps().catch(() => {});
|
|
7926
8056
|
refreshGovernance().catch(() => {});
|
|
7927
8057
|
} finally {
|
|
7928
|
-
|
|
8058
|
+
renderAiConsole();
|
|
7929
8059
|
}
|
|
7930
8060
|
}
|
|
7931
8061
|
|
|
@@ -7961,6 +8091,9 @@ HTML = r"""<!doctype html>
|
|
|
7961
8091
|
<div class="caps-preset-card-stats">${stats}</div>
|
|
7962
8092
|
</button>`;
|
|
7963
8093
|
}).join('');
|
|
8094
|
+
if ($('aiGovernanceSection')?.classList.contains('ai-section-unavailable')) {
|
|
8095
|
+
el.querySelectorAll('button').forEach(button => { button.disabled = true; });
|
|
8096
|
+
}
|
|
7964
8097
|
}
|
|
7965
8098
|
function renderGovUsage() {
|
|
7966
8099
|
const el = $('aiGovUsage');
|
|
@@ -11780,6 +11913,8 @@ HTML = r"""<!doctype html>
|
|
|
11780
11913
|
</html>
|
|
11781
11914
|
"""
|
|
11782
11915
|
|
|
11916
|
+
HTML = HTML.replace("/* CONTROL_PANEL_I18N */", localization_script())
|
|
11917
|
+
|
|
11783
11918
|
|
|
11784
11919
|
def _safe_call(obj: Any, method_name: str, *args: Any) -> Tuple[Any, Optional[str]]:
|
|
11785
11920
|
if obj is None or not hasattr(obj, method_name):
|
|
@@ -11891,6 +12026,8 @@ def _resolve_ai_features(resolve: Any) -> Dict[str, Any]:
|
|
|
11891
12026
|
"generate_speech": has(project, "GenerateSpeech"),
|
|
11892
12027
|
"perform_audio_classification": has(folder, "PerformAudioClassification"),
|
|
11893
12028
|
"clear_audio_classification": has(folder, "ClearAudioClassification"),
|
|
12029
|
+
"transcribe_audio": has(folder, "TranscribeAudio"),
|
|
12030
|
+
"clear_transcription": has(folder, "ClearTranscription"),
|
|
11894
12031
|
"analyze_for_intellisearch": has(folder, "AnalyzeForIntellisearch"),
|
|
11895
12032
|
"analyze_for_slate": has(folder, "AnalyzeForSlate"),
|
|
11896
12033
|
"remove_motion_blur": has(folder, "RemoveMotionBlur"),
|