davinci-resolve-mcp 4.9.3 → 4.10.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 +49 -0
- package/README.md +1 -1
- package/README.zh-CN.md +2 -2
- package/docs/SKILL.md +8 -0
- package/docs/guides/subtitle-track-editing.md +227 -0
- package/docs/kernels/audio-fairlight-kernel.md +5 -0
- package/docs/reference/api-limitations.md +6 -6
- package/install.py +1 -1
- package/package.json +1 -1
- package/resolve-advanced/README.md +6 -0
- package/resolve-advanced/server/db-patch.mjs +99 -18
- package/resolve-advanced/server/subtitle-db.mjs +285 -0
- package/resolve-advanced/server/tools/project_db.mjs +11 -2
- package/resolve-advanced/vendor/drp-format/subtitle-captions.js +122 -0
- package/resolve-advanced/vendor/drp-format/subtitle-preset.js +131 -0
- package/resolve-advanced/vendor/drp-format/subtitle-style.js +5 -1
- package/src/granular/common.py +1 -1
- package/src/granular/timeline_item.py +3 -0
- package/src/server.py +1 -1
- package/src/utils/api_truth.py +49 -21
- package/src/utils/destructive_hook.py +10 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,55 @@
|
|
|
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 v4.10.0 — subtitle caption and preset editing in saved projects
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **Subtitle editing through `project_db`.** Resolve's scripting API can
|
|
10
|
+
generate and list subtitles but cannot change caption text, word timing or
|
|
11
|
+
a track's animation controls (`TimelineItem.SetProperty` does not reach
|
|
12
|
+
them). Six new actions edit them in a saved local project's `Project.db`:
|
|
13
|
+
- `list_captions`, `check_captions` and `write_captions`: caption text,
|
|
14
|
+
frame bounds and explicit per-word timing; add, replace and delete in one
|
|
15
|
+
transaction, preserving unknown fields and the original AI words.
|
|
16
|
+
- `list_subtitle_presets`, `copy_subtitle_preset` and
|
|
17
|
+
`set_subtitle_preset`: clone a compatible saved animation preset, and edit
|
|
18
|
+
Word Highlight's font, position, text / highlight / outline colours and
|
|
19
|
+
outline thickness.
|
|
20
|
+
- Writes require Resolve **fully quit** (checked through the process list,
|
|
21
|
+
plus `iConfirmProjectClosed:true`), take a unique SQLite snapshot backup
|
|
22
|
+
including committed WAL pages, refuse unsupported schemas, and run the edit
|
|
23
|
+
and its readback in one transaction. `verified:true` means database
|
|
24
|
+
readback, not a render. SQLite uses `better-sqlite3`, or `node:sqlite` on
|
|
25
|
+
Node 22.16+ / 23.8+. See `docs/guides/subtitle-track-editing.md`.
|
|
26
|
+
- A live `TimelineItem.SetProperty` on a subtitle item is refused before the
|
|
27
|
+
automatic timeline archive, with a pointer to these actions.
|
|
28
|
+
|
|
29
|
+
Contributed by @sidevconcept (#273).
|
|
30
|
+
|
|
31
|
+
### Changed during review
|
|
32
|
+
|
|
33
|
+
- **No writes while Resolve runs.** An opt-in to write a project that is not
|
|
34
|
+
currently loaded was removed. Measured on Studio 19.1.3.7: a project loaded
|
|
35
|
+
earlier in the session is served from memory when loaded again, so a disk
|
|
36
|
+
write to it is not shown, and saving after editing the same rows
|
|
37
|
+
overwrites it. A fresh launch does read it.
|
|
38
|
+
- **Text colour.** The writer set the Word Highlight macro's `Clone` colour
|
|
39
|
+
controls, which do not render, leaving the template's default pale yellow;
|
|
40
|
+
it now writes the Text+ shading inputs (`Red1`/`Green1`/`Blue1`/`Alpha1`).
|
|
41
|
+
- **Handle release.** A guarded database is closed when a schema query throws,
|
|
42
|
+
a likely cause of the slow tests reported on Windows.
|
|
43
|
+
|
|
44
|
+
### Validation
|
|
45
|
+
|
|
46
|
+
- Contributor-validated on Studio 21.1.0.17 / Windows: a synthetic caption
|
|
47
|
+
edit, full quit, database write, relaunch and burn-in render passed on all
|
|
48
|
+
630 decoded frames, with white base text, yellow highlighting and exact
|
|
49
|
+
word transitions, checked against a natively configured control. Not
|
|
50
|
+
rendered on this project's 19.1.3.7 host.
|
|
51
|
+
- Offline: Node advanced suite 1,036 tests / 0 failed in a clean worktree;
|
|
52
|
+
Python suite green.
|
|
53
|
+
|
|
5
54
|
## What's New in v4.9.3 — .drp bin registry for two or more bins; broader temp-root guard tests
|
|
6
55
|
|
|
7
56
|
### Fixed
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
English | [简体中文](README.zh-CN.md)
|
|
4
4
|
|
|
5
|
-
[](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
|
|
6
6
|
[](https://www.npmjs.com/package/davinci-resolve-mcp)
|
|
7
7
|
[](docs/reference/api-coverage.md)
|
|
8
8
|
[-blue.svg)](#server-modes)
|
package/README.zh-CN.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[English](README.md) | 简体中文
|
|
4
4
|
|
|
5
|
-
[](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
|
|
6
6
|
[](https://www.npmjs.com/package/davinci-resolve-mcp)
|
|
7
7
|
[](docs/reference/api-coverage.md)
|
|
8
8
|
[-blue.svg)](#服务器模式)
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
[](https://www.python.org/downloads/)
|
|
13
13
|
[](https://opensource.org/licenses/MIT)
|
|
14
14
|
|
|
15
|
-
> 本翻译对应 v4.
|
|
15
|
+
> 本翻译对应 v4.10.0 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
|
|
16
16
|
|
|
17
17
|
一个 Model Context Protocol (MCP) 服务器,让 AI 助手通过官方脚本 API 控制 DaVinci Resolve Studio(达芬奇)。它提供完整的 API 覆盖,外加带护栏的工作流助手,涵盖剪辑、媒体池整理、渲染设置、审阅标记、调色、Fusion、Fairlight、项目生命周期任务、扩展开发,以及不碰源媒体的媒体分析。
|
|
18
18
|
|
package/docs/SKILL.md
CHANGED
|
@@ -418,6 +418,14 @@ Operating rules an agent must know:
|
|
|
418
418
|
`iConfirmProjectClosed:true`; every write auto-backs-up and read-back
|
|
419
419
|
verifies. Resolve caches open projects in memory: after patching, fully QUIT
|
|
420
420
|
and relaunch Resolve or the patch will not be visible.
|
|
421
|
+
- **Subtitle DB edits**: `project_db` `list_captions` / `write_captions` /
|
|
422
|
+
`check_captions` cover text, bounds, explicit per-word timing, add and delete.
|
|
423
|
+
`list_subtitle_presets` / `copy_subtitle_preset` / `set_subtitle_preset` handle
|
|
424
|
+
animated Fusion holders separately from basic `list_subtitle_styles`.
|
|
425
|
+
New subtitle writes enforce full Resolve quit BEFORE patching, snapshot the
|
|
426
|
+
SQLite DB and verify transactionally. See
|
|
427
|
+
[subtitle-track editing](guides/subtitle-track-editing.md) for selectors,
|
|
428
|
+
Word Highlight controls, dry-run examples and live-validation limits.
|
|
421
429
|
- **Guards are load-bearing.** Advanced tools refuse rather than fabricate
|
|
422
430
|
(silent-lie guards): a thrown "refused" error usually means wrong input space,
|
|
423
431
|
log-encoded frames, or missing media — read the message before retrying.
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# Subtitle tracks: captions and animated presets
|
|
2
|
+
|
|
3
|
+
The advanced server's `project_db` tool edits saved local SQLite projects.
|
|
4
|
+
The live server can generate subtitles with `timeline_ai.create_subtitles`,
|
|
5
|
+
enumerate their items, read names/bounds and delete items. It cannot edit caption
|
|
6
|
+
text, word timings or animation controls through `TimelineItem.SetProperty`.
|
|
7
|
+
Such subtitle property writes are now refused before auto-archiving.
|
|
8
|
+
|
|
9
|
+
There are two independent kinds of subtitle styling:
|
|
10
|
+
|
|
11
|
+
- `list_subtitle_styles` / `set_subtitle_style` read/edit the basic track
|
|
12
|
+
`EffectFiltersBA` font and position. `styled:false` does **not** mean the
|
|
13
|
+
track has no animation preset.
|
|
14
|
+
- `list_subtitle_presets` / `copy_subtitle_preset` operate on the linked Fusion
|
|
15
|
+
holder and composition. `set_subtitle_preset` edits named Word Highlight
|
|
16
|
+
controls inside its nested compressed tool section.
|
|
17
|
+
|
|
18
|
+
## Required workflow
|
|
19
|
+
|
|
20
|
+
1. Generate captions in Resolve and save the project. Inspect it with
|
|
21
|
+
`list_captions`, `list_subtitle_presets` and `check_captions`.
|
|
22
|
+
2. Preview edits with `dryRun:true`. This reads the saved database; unsaved
|
|
23
|
+
Resolve changes are not visible. Preview IDs for proposed new captions are
|
|
24
|
+
provisional.
|
|
25
|
+
3. Save and **fully quit Resolve before writing**. New subtitle write actions
|
|
26
|
+
require `iConfirmProjectClosed:true` and independently inspect running
|
|
27
|
+
processes. Closing only the project is insufficient. Failure to inspect
|
|
28
|
+
processes also refuses the write. Loading a *different* project is not
|
|
29
|
+
enough either: measured on Studio 19.1.3.7, a project loaded earlier in the
|
|
30
|
+
session is served from memory when it is loaded again, so a disk write to
|
|
31
|
+
it is not shown, and saving after editing the same rows overwrites it.
|
|
32
|
+
4. Apply the edits, then relaunch Resolve and inspect/render the result.
|
|
33
|
+
|
|
34
|
+
Each new subtitle write creates a unique `Project.db.subtitle-<time>-<uuid>.bak`
|
|
35
|
+
SQLite snapshot, including committed WAL pages. Edits and readback verification
|
|
36
|
+
run in one transaction: a failure rolls everything back. Preserve the returned
|
|
37
|
+
backup. To recover, quit Resolve and restore that snapshot using your normal
|
|
38
|
+
project-library recovery procedure; do not replace a live database.
|
|
39
|
+
|
|
40
|
+
`verified:true` means **database readback**, not that Resolve rendered the
|
|
41
|
+
change. Writes never touch source media. Shared/unsupported item dependencies,
|
|
42
|
+
ambiguous timelines, missing tracks and mismatched schemas are refused.
|
|
43
|
+
|
|
44
|
+
## Selectors and discovery
|
|
45
|
+
|
|
46
|
+
Use `projectName` or an explicit `projectDb` path. Discovery searches the
|
|
47
|
+
standard Windows `%APPDATA%/Blackmagic Design/DaVinci Resolve/Support`, macOS
|
|
48
|
+
Application Support (including the App Store sandbox), and Linux
|
|
49
|
+
`~/.local/share/DaVinciResolve` libraries. Both `Resolve Project Library` and
|
|
50
|
+
`Resolve Disk Database` layouts are considered. Relocated libraries need an
|
|
51
|
+
explicit path; these subtitle actions do not operate on Postgres libraries.
|
|
52
|
+
|
|
53
|
+
`timeline` is the saved timeline name; `track` is the 1-based subtitle-track
|
|
54
|
+
index (default 1). Vector associations determine track order. Duplicate timeline
|
|
55
|
+
names require disambiguation in Resolve before editing.
|
|
56
|
+
|
|
57
|
+
SQLite uses `better-sqlite3` when its native binding loads, otherwise
|
|
58
|
+
[`node:sqlite`](https://nodejs.org/docs/latest-v24.x/api/sqlite.html) on a suitable
|
|
59
|
+
Node runtime (22.16+ or 23.8+, which provide `node:sqlite` `backup()`; tested on
|
|
60
|
+
24.14.1 and 22.22.3). Zstd reads use native
|
|
61
|
+
`node:zlib` when available and otherwise `fzstd`. Caption writes emit the raw
|
|
62
|
+
`0x80` envelope, avoiding a native compression dependency.
|
|
63
|
+
|
|
64
|
+
## Captions
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{"action":"list_captions","args":{"projectName":"My Project","timeline":"Reel"}}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Results contain `fps`, `timeUnit:"timeline_frames"` and `captions` with
|
|
71
|
+
`id`, `text`, `start`, `end`, `words:[{text,start,end}]`, `originalWords`,
|
|
72
|
+
`anchor` and any `decodeIssues`. Bounds are absolute timeline frames, with
|
|
73
|
+
exclusive ends. Word frames may be fractional because storage uses 60 ticks
|
|
74
|
+
per second. Caption starts/ends must be nonnegative integer frames.
|
|
75
|
+
|
|
76
|
+
Current words come from protobuf fields 14/15/16; original AI words from
|
|
77
|
+
18/19/20. Field 21 is a frame anchor. The decoder accounts for a caption moved
|
|
78
|
+
after that anchor was written. `Name` is only a fallback: UI text edits can
|
|
79
|
+
leave it stale, and can produce inconsistent word intervals. No assumption is
|
|
80
|
+
made that saving in Resolve will repair those intervals.
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"action":"write_captions",
|
|
85
|
+
"args":{
|
|
86
|
+
"projectName":"My Project", "timeline":"Reel", "dryRun":true,
|
|
87
|
+
"replace":[{
|
|
88
|
+
"id":"<caption-id>", "text":"Hello world", "start":108000, "end":108060,
|
|
89
|
+
"words":[{"text":"Hello","start":108000,"end":108030},
|
|
90
|
+
{"text":"world","start":108030,"end":108060}]
|
|
91
|
+
}],
|
|
92
|
+
"add":[{
|
|
93
|
+
"text":"Welcome", "start":108090, "end":108120,
|
|
94
|
+
"words":[{"text":"Welcome","start":108090,"end":108120}]
|
|
95
|
+
}],
|
|
96
|
+
"delete":["<another-caption-id>"]
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
For a write, replace `dryRun:true` with `iConfirmProjectClosed:true` after
|
|
102
|
+
quitting Resolve. `replace` supplies complete caption content/timing, not a
|
|
103
|
+
partial patch. Text must equal trimmed words joined with spaces. New captions
|
|
104
|
+
clone a caption already on that track (optionally `templateCaptionId`) to
|
|
105
|
+
preserve the unknown schema fields; adding to a completely empty track is
|
|
106
|
+
currently refused. Replacements preserve original AI words and unknown fields;
|
|
107
|
+
additions clear inherited AI history and markers. `Items` associations are
|
|
108
|
+
re-indexed by time without modifying `FusionCompHolderItems`.
|
|
109
|
+
|
|
110
|
+
Invalid word intervals, gaps, overlaps, conflicting IDs, or captions that
|
|
111
|
+
overlap are rejected. Boundaries are quantised to 60 Hz before verification;
|
|
112
|
+
a word that becomes zero length after rounding is also rejected. Existing
|
|
113
|
+
untouched captions are reported by QC rather than silently retimed.
|
|
114
|
+
|
|
115
|
+
`check_captions` takes the same selectors and optional `maxCharacters` (24 by
|
|
116
|
+
default). It reports gaps, invalid/missing timings, overlaps, out-of-bounds
|
|
117
|
+
words, decode count mismatches and long lines. The character limit is a
|
|
118
|
+
heuristic for vertical captions, **not** a pixel-width calculation: font,
|
|
119
|
+
size and position determine actual clipping. It never rewrites timing.
|
|
120
|
+
|
|
121
|
+
## Animated preset copying and controls
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{
|
|
125
|
+
"action":"copy_subtitle_preset",
|
|
126
|
+
"args":{
|
|
127
|
+
"sourceProject":"Reference", "sourceTimeline":"Reel", "sourceTrack":1,
|
|
128
|
+
"projectName":"My Project", "timeline":"Reel", "track":1,
|
|
129
|
+
"replace":false, "dryRun":true,
|
|
130
|
+
"inputs":{
|
|
131
|
+
"font":"Open Sans", "fontStyle":"Semibold", "size":0.08,
|
|
132
|
+
"position":[0.5,0.3],
|
|
133
|
+
"textRed":1, "textGreen":1, "textBlue":1, "textAlpha":1,
|
|
134
|
+
"highlightRed":1, "highlightGreen":0.8, "highlightBlue":0,
|
|
135
|
+
"outlineEnabled":1, "outlineRed":0, "outlineGreen":0, "outlineBlue":0,
|
|
136
|
+
"thickness":0.03
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`sourceProjectDb` can replace `sourceProject`. For compatibility with the
|
|
143
|
+
workflow brief, `source_project`, `source_timeline`, `target_project` and
|
|
144
|
+
`target_timeline` alias those project/timeline selectors; `project` aliases
|
|
145
|
+
`projectName`. Do not pass an alias and its canonical selector together.
|
|
146
|
+
|
|
147
|
+
Copying clones the holder, its track association and its composition with new
|
|
148
|
+
UUIDs. An occupied destination requires `replace:true`; the source is unchanged.
|
|
149
|
+
Omit `inputs` to copy the preset bytes unchanged. To adjust an existing preset,
|
|
150
|
+
use `set_subtitle_preset` with target selectors and `inputs`.
|
|
151
|
+
|
|
152
|
+
Colour channels are 0–1, `outlineEnabled` is 0 or 1, `size` is Fusion's
|
|
153
|
+
normalised text size (not points), `position` is Fusion Center `[x,y]`, and
|
|
154
|
+
`thickness` is the template's HOutlineThickness value. Parameter editing is
|
|
155
|
+
restricted to the inspected Word Highlight template and literal Template
|
|
156
|
+
TextPlus inputs. Connected/animated inputs and unsupported layouts are refused.
|
|
157
|
+
Missing input values in the listing mean Resolve/template defaults, not zero.
|
|
158
|
+
Other supported composition envelopes can be cloned unchanged, but cannot have
|
|
159
|
+
their parameter meanings guessed. Preset holder Duration is preserved from the
|
|
160
|
+
reference (observed 150); it is not extended to caption-track length.
|
|
161
|
+
|
|
162
|
+
## Evidence and outstanding live acceptance
|
|
163
|
+
|
|
164
|
+
On 2026-10-02 a read-only probe on Studio 21.1.0.17/Windows confirmed the API
|
|
165
|
+
boundary on an 11-caption timeline. Saved local project inspection confirmed
|
|
166
|
+
the caption encoding and Word Highlight's nested zlib inputs. Synthetic tests
|
|
167
|
+
exercise both codecs, cloning, transactional rollback, frame-rate arithmetic,
|
|
168
|
+
guard refusal, backup readback and preservation of unrelated tracks.
|
|
169
|
+
|
|
170
|
+
On 2026-10-06, a synthetic-only caption edit → full quit → database write →
|
|
171
|
+
relaunch → BurnIn render acceptance passed on Studio 21.1.0.17 / Windows.
|
|
172
|
+
Seven generated captions were edited through the actual `project_db` handler:
|
|
173
|
+
six replacements, one deletion and one addition, with explicit word boundaries.
|
|
174
|
+
A fresh native Word Highlight preset was created in the disposable project;
|
|
175
|
+
no personal project or media was used as a preset reference.
|
|
176
|
+
|
|
177
|
+
The 21-second 1080x1920 / 30 fps render contained 630 frames. All 420 caption
|
|
178
|
+
frames contained text, all seven word transitions matched the written timings,
|
|
179
|
+
and no caption pixels touched the frame edges or appeared in the gaps. The
|
|
180
|
+
rendered text was visually inspected for all seven captions in both highlight
|
|
181
|
+
states. Resolve's live text/bounds and saved word timing readback also matched.
|
|
182
|
+
The original frame check did not assert base text colour. The maintainer's
|
|
183
|
+
[acceptance review](https://github.com/samuelgursky/davinci-resolve-mcp/pull/273#issuecomment-6048433383)
|
|
184
|
+
found that the non-highlighted word remained pale yellow. The codec now targets
|
|
185
|
+
TextPlus shading inputs (`Red1`, `Green1`, `Blue1`, `Alpha1`) rather than the
|
|
186
|
+
macro's `Clone` controls. On 2026-10-08, the original track's Inspector confirmed
|
|
187
|
+
`#ffeb85` text. A fresh native Word Highlight, configured through Resolve's UI
|
|
188
|
+
with `#ffffff` text and `#ffff00` highlight, rendered white. After a full quit,
|
|
189
|
+
the corrected MCP write and relaunch also rendered white with Segoe UI Black,
|
|
190
|
+
centred position and black outline at thickness 0.1.
|
|
191
|
+
|
|
192
|
+
Both new 1080x1920 renders passed checks on all 630 decoded frames: all 420
|
|
193
|
+
caption frames had white base text and yellow highlighting, with exact word
|
|
194
|
+
transitions and no text in the gaps or at the frame edges. Bright fill pixels
|
|
195
|
+
were measured independently per word, and the original render failed the new
|
|
196
|
+
white-text assertion on all 420 caption frames. All seven captions were also
|
|
197
|
+
visually reviewed in both highlight states; the pixel check is not OCR. Selected
|
|
198
|
+
frame PNGs retain the full render resolution. Generated frames and reports are
|
|
199
|
+
kept outside the repository. The earlier green-pixel live harness was removed
|
|
200
|
+
because its pass flag did not establish word timing or text colour.
|
|
201
|
+
|
|
202
|
+
The earlier failed synthetic render prompted a codec regression fix: inserting
|
|
203
|
+
absent controls after a final input without a trailing comma produced invalid
|
|
204
|
+
Lua. The writer now prepends comma-terminated inputs, and a regression fixture
|
|
205
|
+
covers the missing separator. The new acceptance run exercises this path.
|
|
206
|
+
|
|
207
|
+
That live run also exposed Resolve's association constraints: DbIndex must be
|
|
208
|
+
nonnegative and its composite primary key uses ON CONFLICT REPLACE. The writer
|
|
209
|
+
now rebuilds only the selected track's Items vector within the transaction,
|
|
210
|
+
avoiding both rejected temporary indices and lost neighbours during reordering.
|
|
211
|
+
The regression fixture includes those constraints.
|
|
212
|
+
|
|
213
|
+
To repeat acceptance, use a disposable project and synthetic speech/media:
|
|
214
|
+
generate subtitles, apply Word Highlight with explicit colours, correct two
|
|
215
|
+
captions, add one and delete one, reopen Resolve, then render with
|
|
216
|
+
`ExportSubtitle:true, SubtitleFormat:"BurnIn"`. Inspect rendered frames across
|
|
217
|
+
every word boundary and at the frame edges. Readback alone cannot establish
|
|
218
|
+
visible highlighting, absence of blank frames, or safe margins. Assert the
|
|
219
|
+
non-highlighted text colour separately from the highlighted word, check every
|
|
220
|
+
decoded frame against the written word intervals, and check the caption gaps.
|
|
221
|
+
Review the rendered words visually too: pixel colour checks are not OCR.
|
|
222
|
+
|
|
223
|
+
Also still unverified: holder Duration semantics, per-caption preset rows,
|
|
224
|
+
whether/when UI edits are retimed on save, and SRT import preserving word
|
|
225
|
+
timings on 21.1. A previously reported ImportMedia(SRT)+AppendToTimeline route
|
|
226
|
+
on 21.0.4.5 is not evidence that SRT carries word timings on another build.
|
|
227
|
+
Repeat the probe and render test after Resolve updates.
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Audio / Fairlight Kernel
|
|
2
2
|
|
|
3
|
+
For caption text, per-word timing, additions/deletions and animated subtitle
|
|
4
|
+
presets, use advanced `project_db` after saving and fully quitting Resolve.
|
|
5
|
+
See [subtitle-track editing](../guides/subtitle-track-editing.md). The basic
|
|
6
|
+
`set_subtitle_style` action does not edit animated Fusion presets.
|
|
7
|
+
|
|
3
8
|
The Audio / Fairlight kernel expands `timeline` into a safer audio-state,
|
|
4
9
|
mapping, voice isolation, auto-sync, transcription, subtitle, and Fairlight
|
|
5
10
|
boundary layer.
|
|
@@ -167,22 +167,22 @@ equivalent, blocking full automation.
|
|
|
167
167
|
### Per-subtitle text content and timing editing
|
|
168
168
|
|
|
169
169
|
- **Object:** `TimelineItem (subtitle track)`
|
|
170
|
-
- **Behavior:**
|
|
171
|
-
- **Workaround / current handling:**
|
|
170
|
+
- **Behavior:** GetName, GetStart and GetEnd read caption names and bounds, and Timeline.DeleteClips can delete captions. There are no public methods to set caption text or per-word timings. Read-only live probe on Studio 21.1.0.17 (Windows, 2026-10-02): GetType=generator, GetProperty returned an empty dict, GetFusionCompCount=0, and dir() exposed no caption/text methods; the supplied reproduction also reports false GetProperty/SetProperty results. A subtitle SetProperty request must be refused before timeline auto-archiving. In the supplied 21.1 reproduction, UI text edits leave Name stale and move original AI words/times to protobuf f18/19/20; current f14/15/16 may contain invalid intervals. Whether a later save re-times them is not established here.
|
|
171
|
+
- **Workaround / current handling:** Use advanced project_db list_captions / write_captions / check_captions on a local SQLite project with Resolve fully quit. Current text comes from f14, not stale Name. f15/16 and f19/20 use 60 ticks/sec; f21 is the original caption frame anchor. The tool returns timeline-frame times rebased to the item's current Start. Writes need explicit word times, preserve unknown fields, reject gaps/zero durations and re-index Items atomically, with backup and readback. This is an unsupported DB workaround; render/reopen validation of the implementation remains required before release.
|
|
172
172
|
- **Tags:** missing-method, subtitle, text, timing
|
|
173
173
|
|
|
174
174
|
### Subtitle track styling and presets
|
|
175
175
|
|
|
176
176
|
- **Object:** `TimelineItem / Timeline / Project`
|
|
177
|
-
- **Behavior:** There is no API method to set or query subtitle font family, font size, text color, background color, outline, shadow, position, alignment, or to apply/query subtitle style presets.
|
|
178
|
-
- **Workaround / current handling:** No API workaround exists, but the style IS reachable below the API: it lives in Sm2TiTrack.FieldsBlob for Type=2 tracks, as an EffectFiltersBA payload whose effect 136 carries a Qt QFont descriptor (param 18) and a normalised position vector (param 17). Exposed as project_db list_subtitle_styles / set_subtitle_style (font family/size/weight/italic + position). Confirmed live on BOTH editions 2026-08-06 — Studio 19.1.3 and free 21.0.3, identical behaviour: Resolve opens a patched track and re-serialises it back to its own zstd form with the patched values intact, so it genuinely parses the write. Caveats: whole-TRACK style not per-caption, project must be CLOSED, Resolve must be fully quit and relaunched afterwards, and the track must already carry a style blob (a freshly added subtitle track has none until it is styled once in the UI). Burn-in overlays via Fusion titles remain a visual alternative but do not produce subtitle tracks.
|
|
177
|
+
- **Behavior:** There is no API method to set or query subtitle font family, font size, text color, background color, outline, shadow, position, alignment, or to apply/query subtitle style presets. Older probes returned only transform/composite keys; a read-only Studio 21.1.0.17 probe returned an empty dict. Timeline.GetSetting() and Project.GetSetting() return None for all probed subtitle-style keys (e.g. 'subtitleFontName', 'subtitleFontSize', 'subtitleTextColor', 'subtitleBackgroundColor', 'subtitlePosition', 'subtitleAlignment', 'subtitlePreset', 'subtitleStyle'). Verified via dir(), GetProperty(), and GetSetting() on Resolve 21.0.0.48.
|
|
178
|
+
- **Workaround / current handling:** No API workaround exists, but the style IS reachable below the API: it lives in Sm2TiTrack.FieldsBlob for Type=2 tracks, as an EffectFiltersBA payload whose effect 136 carries a Qt QFont descriptor (param 18) and a normalised position vector (param 17). Exposed as project_db list_subtitle_styles / set_subtitle_style (font family/size/weight/italic + position). Confirmed live on BOTH editions 2026-08-06 — Studio 19.1.3 and free 21.0.3, identical behaviour: Resolve opens a patched track and re-serialises it back to its own zstd form with the patched values intact, so it genuinely parses the write. Caveats: whole-TRACK style not per-caption, project must be CLOSED, Resolve must be fully quit and relaunched afterwards, and the track must already carry a style blob (a freshly added subtitle track has none until it is styled once in the UI). Burn-in overlays via Fusion titles remain a visual alternative but do not produce subtitle tracks. Animated Word Highlight is DIFFERENT: list_subtitle_styles may report styled:false while an animation is present. Sm2TiItem_Sm2TiTrack links a Sm2TiVideoClip holder via FusionCompHolderItems to a Sm2TiCompositionTable. The supplied Studio 21.1.0.17 reproduction verified cloning these three rows; local read-only inspection corroborated the format. Use list_subtitle_presets / copy_subtitle_preset, or set_subtitle_preset for named Word Highlight inputs inside the nested zlib tool section. These new actions check that Resolve is fully quit BEFORE writing. Holder Duration is preserved; its rendering semantics and per-caption preset layouts remain unverified.
|
|
179
179
|
- **Tags:** missing-method, subtitle, style, preset
|
|
180
180
|
|
|
181
181
|
### Speech recognition engine selection and SRT import
|
|
182
182
|
|
|
183
183
|
- **Object:** `Timeline`
|
|
184
|
-
- **Behavior:** Timeline.CreateSubtitlesFromAudio(autoCaptionSettings) always uses the built-in Resolve speech recognition engine. There is no API parameter to select an alternative provider (e.g. whisper-cli, Google Speech, AWS Transcribe). The language selection via resolve.AUTO_CAPTION_LANGUAGE_* is the only customization; the engine itself cannot be changed.
|
|
185
|
-
- **Workaround / current handling:**
|
|
184
|
+
- **Behavior:** Timeline.CreateSubtitlesFromAudio(autoCaptionSettings) always uses the built-in Resolve speech recognition engine. There is no API parameter to select an alternative provider (e.g. whisper-cli, Google Speech, AWS Transcribe). The language selection via resolve.AUTO_CAPTION_LANGUAGE_* is the only customization; the engine itself cannot be changed. There is no dedicated subtitle import method. However, MediaPool.ImportMedia(srt) + AppendToTimeline was reported working on Studio 21.0.4.5 (issue #169). Whether that route carries word timing for animated presets on 21.1 has not been verified; SRT contains only cue timing.
|
|
185
|
+
- **Workaround / current handling:** Use Resolve's engine for CreateSubtitlesFromAudio. For external SRT, test ImportMedia + AppendToTimeline on the exact installed build or import through the UI. Use project_db write_captions for explicit word timings with Resolve fully quit; do not assume SRT supplies them.
|
|
186
186
|
- **Tags:** missing-method, subtitle, transcription, speech-recognition, asr
|
|
187
187
|
|
|
188
188
|
### Media Pool folder rename
|
package/install.py
CHANGED
|
@@ -37,7 +37,7 @@ from src.utils.update_check import (
|
|
|
37
37
|
|
|
38
38
|
# ─── Version ──────────────────────────────────────────────────────────────────
|
|
39
39
|
|
|
40
|
-
VERSION = "4.
|
|
40
|
+
VERSION = "4.10.0"
|
|
41
41
|
# Only hard floor: mcp[cli] requires Python 3.10+. There is no upper bound —
|
|
42
42
|
# Resolve's scripting bridge loads into newer interpreters on recent builds
|
|
43
43
|
# (Python 3.14 verified against Resolve Studio 20.3.2). Older Resolve builds
|
package/package.json
CHANGED
|
@@ -107,6 +107,12 @@ Each dispatches on an `action`. Highlights:
|
|
|
107
107
|
Includes `list_subtitle_styles` / `set_subtitle_style` — caption font family/size/weight/italic
|
|
108
108
|
and normalised position, which the scripting API cannot touch at all. Whole-track (not
|
|
109
109
|
per-caption); project must be CLOSED and Resolve fully quit + relaunched afterwards.
|
|
110
|
+
Animated presets use separate `list_subtitle_presets` / `copy_subtitle_preset` /
|
|
111
|
+
`set_subtitle_preset` actions; caption text and word timing use `list_captions` /
|
|
112
|
+
`write_captions` / `check_captions`. These SQLite writes enforce full Resolve
|
|
113
|
+
quit before editing, take unique backups and verify in a transaction.
|
|
114
|
+
See [subtitle-track editing](../docs/guides/subtitle-track-editing.md) for
|
|
115
|
+
parameters, Word Highlight controls and the pending live-render acceptance.
|
|
110
116
|
- **`pipeline`** — the DB-as-truth pipeline foundation (see below).
|
|
111
117
|
- **`deliverable`** — deliverable QC / compliance: `deliverable_qc` (ffprobe a render vs its spec →
|
|
112
118
|
pass/fail per field), `loudness_qc` (ebur128 LUFS/true-peak/LRA), `reframe_blanking_check`,
|
|
@@ -6,12 +6,14 @@
|
|
|
6
6
|
* verify. The schema map is Resolve 21 / ProjectVersion 17 (the design notes design notes);
|
|
7
7
|
* the column guard refuses rather than corrupting if the schema differs.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
9
|
+
* Uses optional better-sqlite3 or node:sqlite (lazy, compatible adapter).
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import fs from 'node:fs';
|
|
13
13
|
import path from 'node:path';
|
|
14
14
|
import os from 'node:os';
|
|
15
|
+
import childProcess from 'node:child_process';
|
|
16
|
+
import { randomUUID } from 'node:crypto';
|
|
15
17
|
import { createRequire } from 'node:module';
|
|
16
18
|
|
|
17
19
|
const require = createRequire(import.meta.url);
|
|
@@ -41,9 +43,8 @@ export const PROJECT_LIBRARY_ROOT = path.join(os.homedir(), 'Library/Application
|
|
|
41
43
|
* Project.db found", sending exactly the users the in-app bridge exists to serve
|
|
42
44
|
* hunting for a path they have no reason to know.
|
|
43
45
|
*
|
|
44
|
-
* macOS-only: the sandbox container is an Apple construct.
|
|
45
|
-
*
|
|
46
|
-
* nothing is guessed for those platforms — pass `projectDb` explicitly there.
|
|
46
|
+
* macOS-only: the sandbox container is an Apple construct. Windows/Linux
|
|
47
|
+
* use the platform roots returned by projectLibraryRoots below.
|
|
47
48
|
*/
|
|
48
49
|
export const LITE_DB_ROOT = path.join(
|
|
49
50
|
os.homedir(),
|
|
@@ -51,7 +52,16 @@ export const LITE_DB_ROOT = path.join(
|
|
|
51
52
|
);
|
|
52
53
|
|
|
53
54
|
/** Every root searched when resolving a project by name, Studio first. */
|
|
54
|
-
export
|
|
55
|
+
export function projectLibraryRoots(platform = process.platform, home = os.homedir(), env = process.env) {
|
|
56
|
+
const join = platform === 'win32' ? path.win32.join : path.posix.join;
|
|
57
|
+
const libraries = (base) => ['Resolve Project Library', 'Resolve Disk Database']
|
|
58
|
+
.map((name) => join(base, name, 'Resolve Projects'));
|
|
59
|
+
if (platform === 'win32') return libraries(join(env.APPDATA || join(home, 'AppData', 'Roaming'),
|
|
60
|
+
'Blackmagic Design', 'DaVinci Resolve', 'Support'));
|
|
61
|
+
if (platform === 'linux') return libraries(join(home, '.local', 'share', 'DaVinciResolve'));
|
|
62
|
+
return [DISK_DB_ROOT, PROJECT_LIBRARY_ROOT, LITE_DB_ROOT];
|
|
63
|
+
}
|
|
64
|
+
export const DB_ROOTS = projectLibraryRoots();
|
|
55
65
|
|
|
56
66
|
/**
|
|
57
67
|
* Filter roots to the ones that answer a readdir within `deadlineMs`.
|
|
@@ -92,7 +102,75 @@ export async function responsiveRoots(roots = DB_ROOTS, deadlineMs = 3000) {
|
|
|
92
102
|
}
|
|
93
103
|
|
|
94
104
|
export function loadSqlite() {
|
|
95
|
-
|
|
105
|
+
// A successful require alone doesn't detect a native ABI mismatch: the
|
|
106
|
+
// bindings are loaded by the constructor. Probe before selecting a backend.
|
|
107
|
+
try {
|
|
108
|
+
const Database = requireBetterSqlite3('Project.db patching');
|
|
109
|
+
const probe = new Database(':memory:');
|
|
110
|
+
probe.close();
|
|
111
|
+
return Database;
|
|
112
|
+
} catch (nativeError) {
|
|
113
|
+
// DatabaseSync shipped in 22.13, but snapshotBackup needs sqlite.backup(),
|
|
114
|
+
// added in 22.16 (23.8 on the 23 line). Without it a write would fail only
|
|
115
|
+
// after opening the database, with "backup is not a function".
|
|
116
|
+
let builtin;
|
|
117
|
+
try { builtin = require('node:sqlite'); } catch { builtin = null; }
|
|
118
|
+
if (typeof builtin?.backup !== 'function') {
|
|
119
|
+
throw new Error(`${nativeError.message} Alternatively use Node 22.16+ (or 23.8+), whose node:sqlite provides backup().`);
|
|
120
|
+
}
|
|
121
|
+
return BuiltinSqlite;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Small better-sqlite3-compatible surface used by Project.db consumers. Keep
|
|
126
|
+
// rows as ordinary objects and blobs as Buffers on either backend.
|
|
127
|
+
export class BuiltinSqlite {
|
|
128
|
+
constructor(filename, { readonly = false } = {}) {
|
|
129
|
+
const { DatabaseSync } = require('node:sqlite');
|
|
130
|
+
this.db = new DatabaseSync(filename, { readOnly: readonly, enableForeignKeyConstraints: false });
|
|
131
|
+
}
|
|
132
|
+
exec(sql) { this.db.exec(sql); return this; }
|
|
133
|
+
close() { this.db.close(); }
|
|
134
|
+
prepare(sql) {
|
|
135
|
+
const stmt = this.db.prepare(sql);
|
|
136
|
+
const row = (r) => r && Object.fromEntries(Object.entries(r)
|
|
137
|
+
.map(([k, v]) => [k, v instanceof Uint8Array ? Buffer.from(v) : v]));
|
|
138
|
+
return { get: (...args) => row(stmt.get(...args)),
|
|
139
|
+
all: (...args) => stmt.all(...args).map(row), run: (...args) => stmt.run(...args) };
|
|
140
|
+
}
|
|
141
|
+
transaction(fn) {
|
|
142
|
+
return (...args) => {
|
|
143
|
+
this.exec('BEGIN IMMEDIATE');
|
|
144
|
+
try { const result = fn(...args); this.exec('COMMIT'); return result; }
|
|
145
|
+
catch (error) { this.exec('ROLLBACK'); throw error; }
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
backup(filename) { return require('node:sqlite').backup(this.db, filename); }
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Verify the process is gone; a caller assertion alone is not sufficient. */
|
|
152
|
+
export function requireResolveQuit(opts) {
|
|
153
|
+
requireClosed(opts);
|
|
154
|
+
if (resolveRunning()) throw new Error('Fully QUIT Resolve before subtitle database writes (Resolve is running).');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function resolveRunning() {
|
|
158
|
+
const win = process.platform === 'win32';
|
|
159
|
+
const result = childProcess.spawnSync(win ? 'tasklist.exe' : 'ps',
|
|
160
|
+
win ? ['/FO', 'CSV', '/NH'] : ['-A', '-o', 'comm='],
|
|
161
|
+
{ encoding: 'utf8', timeout: 5000, windowsHide: true });
|
|
162
|
+
if (result.error || result.status !== 0) throw new Error('Cannot verify Resolve is quit; refusing database write.');
|
|
163
|
+
return result.stdout.split(/\r?\n/).some((line) => win
|
|
164
|
+
? /^"Resolve\.exe",/i.test(line.trim())
|
|
165
|
+
: /(^|\/)resolve(?:\.exe)?$/i.test(line.trim()));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** SQLite snapshot includes committed WAL pages; never overwrite an older backup. */
|
|
169
|
+
export async function snapshotBackup(dbPath) {
|
|
170
|
+
const filename = `${dbPath}.subtitle-${Date.now()}-${randomUUID()}.bak`;
|
|
171
|
+
const db = openGuarded(dbPath);
|
|
172
|
+
try { await db.backup(filename); } finally { db.close(); }
|
|
173
|
+
return filename;
|
|
96
174
|
}
|
|
97
175
|
|
|
98
176
|
/**
|
|
@@ -137,7 +215,7 @@ export function resolveDbPath({ projectDb, projectName, roots, skippedRoots = []
|
|
|
137
215
|
'are invisible until macOS unwedges it. '
|
|
138
216
|
: '') +
|
|
139
217
|
'If Resolve keeps its projects elsewhere — a relocated library, a network/Postgres ' +
|
|
140
|
-
'database
|
|
218
|
+
'database — pass projectDb with the full local SQLite path (Postgres is not a Project.db file).',
|
|
141
219
|
);
|
|
142
220
|
}
|
|
143
221
|
if (hits.length > 1) {
|
|
@@ -153,19 +231,22 @@ export function openGuarded(dbPath, { writable = false, table, column } = {}) {
|
|
|
153
231
|
const Database = loadSqlite();
|
|
154
232
|
if (!fs.existsSync(dbPath)) throw new Error(`Project.db not found: ${dbPath}`);
|
|
155
233
|
const db = new Database(dbPath, { readonly: !writable });
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
234
|
+
try {
|
|
235
|
+
if (table && column) {
|
|
236
|
+
// Quote identifiers: Resolve table names can contain "::".
|
|
237
|
+
const cols = db
|
|
238
|
+
.prepare(`PRAGMA table_info("${String(table).replace(/"/g, '""')}")`)
|
|
239
|
+
.all()
|
|
240
|
+
.map((c) => c.name);
|
|
241
|
+
if (!cols.includes(column)) {
|
|
242
|
+
throw new Error(`${table}.${column} not found — unsupported Project.db schema/version; refusing to patch.`);
|
|
243
|
+
}
|
|
166
244
|
}
|
|
245
|
+
return db;
|
|
246
|
+
} catch (error) {
|
|
247
|
+
db.close();
|
|
248
|
+
throw error;
|
|
167
249
|
}
|
|
168
|
-
return db;
|
|
169
250
|
}
|
|
170
251
|
|
|
171
252
|
export function backup(dbPath) {
|