davinci-resolve-mcp 2.93.2 → 2.94.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 +112 -0
- package/README.md +1 -1
- package/README.zh-CN.md +2 -2
- package/docs/reference/api-limitations.md +4 -4
- package/install.py +1 -1
- package/package.json +1 -1
- package/src/granular/common.py +1 -1
- package/src/server.py +1 -1
- package/src/utils/api_truth.py +71 -33
- package/src/utils/delivery_targets.py +109 -12
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,118 @@
|
|
|
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.94.0
|
|
6
|
+
|
|
7
|
+
Adds four delivery targets for deliverable classes the table did not cover:
|
|
8
|
+
image sequences for web/graphics, animated web assets, and an HTTP Live
|
|
9
|
+
Streaming package. All 32 targets resolve live on Studio 21.0.4.5.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **`png_sequence`** — PNG frames for web and motion-graphics handoff. Resolve
|
|
14
|
+
exposes **RGB only** (no alpha codec), so the target says so and points at
|
|
15
|
+
`dpx_sequence` (`RGBA 8 bits`) or `prores4444_master` when transparency is
|
|
16
|
+
actually needed. Alias: `png`.
|
|
17
|
+
- **`gif_animated`** — looping animated GIF. No audio track at all, so
|
|
18
|
+
`ExportAudio` is False rather than merely unpinned. Its QC projection uses
|
|
19
|
+
ffprobe values measured on a generated file (`container: gif`, `codec: gif`).
|
|
20
|
+
Alias: `gif`.
|
|
21
|
+
- **`webp_animated`** — looping animated WebP. Ships with **no QC projection**:
|
|
22
|
+
the reference machine's ffmpeg has only the `webp_pipe` still-image demuxer, so
|
|
23
|
+
a rendered animated WebP could not be probed and the ffprobe values are
|
|
24
|
+
unmeasured. `qc_skip_reason` says exactly that. Guessing `webp`/`webp` would
|
|
25
|
+
have produced QC failures about vocabulary rather than about the deliverable —
|
|
26
|
+
the same trap as mp4 reporting its container as `mov`. Alias: `webp`.
|
|
27
|
+
- **`hls_h264`** — HTTP Live Streaming package (`.m3u8` playlist plus segments),
|
|
28
|
+
in the `package` tier alongside IMF and DCP. Bitrate ladders, variant playlists
|
|
29
|
+
and encryption keys are not expressible as a render target. Aliases: `hls`,
|
|
30
|
+
`streaming`.
|
|
31
|
+
|
|
32
|
+
### Notes
|
|
33
|
+
|
|
34
|
+
- PNG, WebP and HLS are **not render formats on Resolve 19.x**. These targets
|
|
35
|
+
resolve on 21.x and fail loudly with the machine's available list on older
|
|
36
|
+
builds, which is the designed behavior rather than a regression.
|
|
37
|
+
- Deliberately **not** added, having checked the full 21.0.4.5 matrix: container
|
|
38
|
+
swaps that duplicate existing capability (MKV carries the same ProRes and
|
|
39
|
+
H.264/H.265 codecs already covered), standalone JPEG 2000 (the same essence the
|
|
40
|
+
IMF/DCP targets reach), and legacy formats (AVI, Cineon, MJ2, Panasonic AVC
|
|
41
|
+
8K). All remain reachable via a `format_candidates` override or a user target.
|
|
42
|
+
- `braw`, `mts` and `wav` expose zero codecs and reject every codec value, so no
|
|
43
|
+
target is possible for them at all.
|
|
44
|
+
|
|
45
|
+
## What's New in v2.93.3
|
|
46
|
+
|
|
47
|
+
Re-verifies the delivery-target table and the two recorded render API
|
|
48
|
+
limitations against the **installed** Resolve build (Studio 21.0.4.5), rather
|
|
49
|
+
than the 19.1.3.7 they were first measured on.
|
|
50
|
+
|
|
51
|
+
### Changed
|
|
52
|
+
|
|
53
|
+
- **Delivery-target provenance now reflects both builds.** `VERIFIED_ON` reads
|
|
54
|
+
21.0.4.5 (23 formats / 326 pairs) with the original 19.1.3.7 measurement
|
|
55
|
+
(20 formats / 271 pairs) kept alongside it. All 28 targets resolve on 21.0.4.5.
|
|
56
|
+
- **The module now records why candidate ordering matters.** Codec **ids were
|
|
57
|
+
stable** across the two majors while **descriptions were not** — every DNx
|
|
58
|
+
description gained an `"Avid "` prefix in 21.x (`"DNxHR HQ"` →
|
|
59
|
+
`"Avid DNxHR HQ 12-bit"`) while `DNxHRHQ`, `DNxHRLB` and `DNxHRHQX_10` did not
|
|
60
|
+
move. The targets that survived the upgrade did so because their candidate list
|
|
61
|
+
contained the real id, so ids now lead every list and descriptions follow.
|
|
62
|
+
- **Corrected a claim that went stale between builds.** The module asserted "PNG
|
|
63
|
+
is not a render format at all", which was true on 19.1.3.7 and is false on
|
|
64
|
+
21.0.4.5 (`png`, `jpg` and `webp` are all render formats there). There is still
|
|
65
|
+
no `png_sequence` target, but the reason is now "not added", not "impossible".
|
|
66
|
+
|
|
67
|
+
### Fixed
|
|
68
|
+
|
|
69
|
+
- **`api_truth.py`: the zero-codec format list was presented as fixed.** It is
|
|
70
|
+
build-specific — `wav` and `gif` on 19.1.3.7, but `braw`, `mts` and `wav` on
|
|
71
|
+
21.0.4.5 (`gif` gained codecs; BRAW and MTS lost them). `wav` is affected on
|
|
72
|
+
both, so an audio-only WAV deliverable remains inexpressible through
|
|
73
|
+
`SetCurrentRenderFormatAndCodec`, which rejects every value including the empty
|
|
74
|
+
string.
|
|
75
|
+
- **The description-vs-id trap is re-confirmed unchanged on 21.0.4.5** —
|
|
76
|
+
`('mp4', 'H.264')` still returns False while `('mp4', 'H264')` returns True,
|
|
77
|
+
two major versions after it was first recorded. The entry now carries evidence
|
|
78
|
+
from both builds and notes that descriptions drift while ids do not.
|
|
79
|
+
|
|
80
|
+
`docs/reference/api-limitations.md` regenerated from those entries.
|
|
81
|
+
|
|
82
|
+
## What's New in v2.93.3
|
|
83
|
+
|
|
84
|
+
Ledger correction, measured live on DaVinci Resolve Studio **21.0.4.5**.
|
|
85
|
+
|
|
86
|
+
- **A native Text+ CAN be placed at an exact track and frame, and stay editable
|
|
87
|
+
— entirely through the public API.** The issue #74 entry has said for two
|
|
88
|
+
months to "accept the limitation." That was wrong about the outcome, and the
|
|
89
|
+
recommendation is replaced. `InsertFusionTitleIntoTimeline` really does take no
|
|
90
|
+
track/frame/duration and really does produce a source-less item — but it is not
|
|
91
|
+
the only door. Put the title on its **own timeline**, then place *that*
|
|
92
|
+
timeline's media pool item (`Type='Timeline'`) with `AppendToTimeline`'s
|
|
93
|
+
clipInfo `trackIndex`/`recordFrame`. Measured: lands on the requested track at
|
|
94
|
+
the requested frame, exactly.
|
|
95
|
+
|
|
96
|
+
- **The text survives the nesting.** `placedItem.GetMediaPoolItem().GetTimeline()`
|
|
97
|
+
(Resolve 21.0.4+) opens the inner timeline; the Text+ there still reports
|
|
98
|
+
`GetFusionCompCount() == 1`, so
|
|
99
|
+
`GetFusionCompByIndex(1).FindTool("Template").SetInput("StyledText", …)` works
|
|
100
|
+
and persists across processes. Duration is controllable the same way —
|
|
101
|
+
`duration = endFrame - startFrame`, `endFrame` exclusive, verified at 1, 119 and
|
|
102
|
+
120 frames. It composes: a PNG plus two titles each placed by `trackIndex` into
|
|
103
|
+
one container timeline, and that container placed as a single clip, with every
|
|
104
|
+
element still individually reachable and editable through the nesting.
|
|
105
|
+
|
|
106
|
+
- **Compound clips are the trap.** `CreateCompoundClip` also gives a source-less
|
|
107
|
+
title a MediaPoolItem and also places correctly — but it **severs the text**.
|
|
108
|
+
`FusionCompCount` drops 1 → 0, and `GetTimeline()` returns `None` for
|
|
109
|
+
`Type='Compound'` while working correctly for `Type='Timeline'`, so nothing gets
|
|
110
|
+
back to the Text+. Nest, never compound, when the text must stay editable.
|
|
111
|
+
|
|
112
|
+
- Two constraints recorded with the route: every placed instance shares one media
|
|
113
|
+
pool item, so a text edit propagates to all of them (use one source timeline per
|
|
114
|
+
distinct card); and placements must not overlap on a track or the append is
|
|
115
|
+
silently rejected.
|
|
116
|
+
|
|
5
117
|
## What's New in v2.93.2
|
|
6
118
|
|
|
7
119
|
Documentation and ledger correction, validated live against DaVinci Resolve
|
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
|
-
> 本翻译对应 v2.
|
|
15
|
+
> 本翻译对应 v2.94.0 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
|
|
16
16
|
|
|
17
17
|
一个 Model Context Protocol (MCP) 服务器,让 AI 助手通过官方脚本 API 控制 DaVinci Resolve Studio(达芬奇)。它提供完整的 API 覆盖,外加带护栏的工作流助手,涵盖剪辑、媒体池整理、渲染设置、审阅标记、调色、Fusion、Fairlight、项目生命周期任务、扩展开发,以及不碰源媒体的媒体分析。
|
|
18
18
|
|
|
@@ -66,8 +66,8 @@ equivalent, blocking full automation.
|
|
|
66
66
|
### Source Track Selector / destination track for Insert*IntoTimeline
|
|
67
67
|
|
|
68
68
|
- **Object:** `Timeline`
|
|
69
|
-
- **Behavior:** There is no API to read or set the Source/Auto Track Selector (the Edit-page patch panel that picks the destination track). InsertTitleIntoTimeline, InsertFusionTitleIntoTimeline, InsertGeneratorIntoTimeline, InsertFusionGeneratorIntoTimeline, InsertOFXGeneratorIntoTimeline and InsertFusionCompositionIntoTimeline take no trackIndex and always drop the clip on the selector's current target (V1 in practice). Locking lower video tracks does NOT redirect the insert — verified live on 21.0.0: locking V1 makes the insert FAIL rather than land on V2. Titles/generators also can't be moved afterward (no MediaPoolItem, so AppendToTimeline clipInfo and MoveClips don't apply). RE-MEASURED on Studio 21.0.4.5 (2026-08-12, version read live), one fresh 3-video-track timeline per arm so no insert could fail on a collision: nothing locked -> lands V1; V1 locked via SetTrackLock -> returns None; V1 locked by CLICKING THE PADLOCK IN THE UI -> returns None, IDENTICAL to the API lock. That kills the recurring theory that the GUI lock advances the selector where the API lock does not — it does not, and a revision of resolve-advanced/vendor/drp-format/README.md that claimed otherwise as 'verified live' has been withdrawn as false. WHAT DOES WORK, and it is the whole mechanism: dragging the SOURCE PATCH badge onto V2 in the Edit-page patch panel sends the next insert to V2 (measured, same rig, nothing locked). The destination is the patch panel, never the lock state. In the track header the per-track badge column is the auto-track-selector toggle, while the source patch badge appears only on the patched track and dragging THAT is what re-targets. So
|
|
70
|
-
- **Workaround / current handling:**
|
|
69
|
+
- **Behavior:** There is no API to read or set the Source/Auto Track Selector (the Edit-page patch panel that picks the destination track). InsertTitleIntoTimeline, InsertFusionTitleIntoTimeline, InsertGeneratorIntoTimeline, InsertFusionGeneratorIntoTimeline, InsertOFXGeneratorIntoTimeline and InsertFusionCompositionIntoTimeline take no trackIndex and always drop the clip on the selector's current target (V1 in practice). Locking lower video tracks does NOT redirect the insert — verified live on 21.0.0: locking V1 makes the insert FAIL rather than land on V2. Titles/generators also can't be moved afterward (no MediaPoolItem, so AppendToTimeline clipInfo and MoveClips don't apply). RE-MEASURED on Studio 21.0.4.5 (2026-08-12, version read live), one fresh 3-video-track timeline per arm so no insert could fail on a collision: nothing locked -> lands V1; V1 locked via SetTrackLock -> returns None; V1 locked by CLICKING THE PADLOCK IN THE UI -> returns None, IDENTICAL to the API lock. That kills the recurring theory that the GUI lock advances the selector where the API lock does not — it does not, and a revision of resolve-advanced/vendor/drp-format/README.md that claimed otherwise as 'verified live' has been withdrawn as false. WHAT DOES WORK, and it is the whole mechanism: dragging the SOURCE PATCH badge onto V2 in the Edit-page patch panel sends the next insert to V2 (measured, same rig, nothing locked). The destination is the patch panel, never the lock state. In the track header the per-track badge column is the auto-track-selector toggle, while the source patch badge appears only on the patched track and dragging THAT is what re-targets. So THE SELECTOR itself is reachable only by GUI automation, and exposing read/write on the patch panel is a smaller, better-defined API request than adding trackIndex to all six Insert*IntoTimeline methods. Do not read that as 'a title cannot be placed on a chosen track from a script' — it can, without touching the selector at all, by nesting it (see the recommendation). The gap is in these six methods and in selector access, not in the outcome.
|
|
70
|
+
- **Workaround / current handling:** USE THE NESTED-TIMELINE ROUTE — the insert method is a dead end but the goal is not, and this is entirely public API. Measured end to end on Studio 21.0.4.5 (2026-08-12): put the title on its OWN timeline (CreateEmptyTimeline + InsertFusionTitleIntoTimeline; it lands on V1 there and that does not matter), then place THAT timeline's media pool item — Timeline.GetMediaPoolItem(), clip property Type='Timeline' — with AppendToTimeline's clipInfo trackIndex/recordFrame. It lands on the requested track at the requested frame, exactly. The text stays settable afterwards: placedItem.GetMediaPoolItem().GetTimeline() (Resolve 21.0.4+) opens the inner timeline, and the Text+ item there still reports GetFusionCompCount()==1, so GetFusionCompByIndex(1).FindTool('Template').SetInput('StyledText', ...) works and persists across processes. Duration is controllable the same way: duration = endFrame - startFrame, endFrame EXCLUSIVE (verified at 1, 119 and 120 frames). This composes — a PNG plus two titles each placed by trackIndex into one container timeline, and the container placed as a single clip, all elements individually reachable and editable through the nesting. TWO CONSTRAINTS: (a) every placed instance shares ONE media pool item, so a text edit propagates to all of them — use one source timeline per distinct card; (b) placements must not overlap on a track, or the append is silently rejected (see the overlapping-record entry). COMPOUND CLIPS ARE THE TRAP HERE: Timeline.CreateCompoundClip also gives a source-less title a MediaPoolItem and also places correctly, but it SEVERS the text — FusionCompCount drops 1->0 and GetTimeline() returns None for Type='Compound' while working correctly for Type='Timeline', so there is no route back to the Text+. Use a nested timeline, never a compound, when the text must stay editable. For clips that already have a MediaPoolItem, AppendToTimeline clipInfo 'trackIndex' was always the answer (exposed as media_pool append_to_timeline clip_infos). Do NOT reach for track locking in either form — it blocks the insert rather than re-targeting it. GUI automation of the patch panel works but is a last resort and is unverifiable from the API side; if used, read the landing track back with TimelineItem.GetTrackTypeAndIndex() and treat a wrong track as a failure. Offline, the advanced server's drp place_fusion_title places a title by track and frame directly. See issue #74.
|
|
71
71
|
- **Reference:** [issue #74](https://github.com/samuelgursky/davinci-resolve-mcp/issues/74)
|
|
72
72
|
- **Tags:** missing-method, timeline, title, generator, track
|
|
73
73
|
|
|
@@ -235,7 +235,7 @@ equivalent, blocking full automation.
|
|
|
235
235
|
|
|
236
236
|
- **Object:** `Project`
|
|
237
237
|
- **Signature:** `(format, codec) -> bool`
|
|
238
|
-
- **Behavior:** Some render formats expose NO codecs at all
|
|
238
|
+
- **Behavior:** Some render formats expose NO codecs at all, and the call then rejects every codec value — the empty string, the format id itself, and any plausible name ('Linear PCM'). Which formats are affected varies by build: 'wav' and 'gif' on Studio 19.1.3.7; 'braw', 'mts' and 'wav' on 21.0.4.5 (gif gained codecs, BRAW and MTS lost them). 'wav' is affected on both, so there is no documented way to select it through this API and an audio-only WAV deliverable is not expressible in scripting.
|
|
239
239
|
- **Workaround / current handling:** Check GetRenderCodecs(format) first; when it is empty, treat the format as unreachable through this API rather than guessing a codec value. Render audio-only via ExportVideo=False on a format that does expose codecs, or drive it from a saved render preset.
|
|
240
240
|
- **Tags:** render, deliver, audio, unsupported
|
|
241
241
|
|
|
@@ -470,7 +470,7 @@ values, or automation-hostile modal prompts.
|
|
|
470
470
|
|
|
471
471
|
- **Object:** `Project`
|
|
472
472
|
- **Signature:** `(renderFormat) -> {codec description: codec name}`
|
|
473
|
-
- **Behavior:** Returns {description: id} — the human-readable description is the KEY and the id Resolve actually accepts is the VALUE. SetCurrentRenderFormatAndCodec, GetRenderCodecs and GetRenderResolutions all require the id, so passing the description a user sees in the Deliver page is rejected. Verified live on Studio 19.1.3.7: ('mov', 'Apple ProRes 422 HQ') -> False while ('mov', 'ProRes422HQ') -> True, and ('mp4', 'H.264') -> False while ('mp4', 'H264') -> True. It affects every family, not only the ones whose id differs obviously. Mirrors the same trap in GetRenderFormats, which returns {format: extension}.
|
|
473
|
+
- **Behavior:** Returns {description: id} — the human-readable description is the KEY and the id Resolve actually accepts is the VALUE. SetCurrentRenderFormatAndCodec, GetRenderCodecs and GetRenderResolutions all require the id, so passing the description a user sees in the Deliver page is rejected. Verified live on Studio 19.1.3.7 and re-confirmed unchanged on 21.0.4.5: ('mov', 'Apple ProRes 422 HQ') -> False while ('mov', 'ProRes422HQ') -> True, and ('mp4', 'H.264') -> False while ('mp4', 'H264') -> True. It affects every family, not only the ones whose id differs obviously. Mirrors the same trap in GetRenderFormats, which returns {format: extension}. Descriptions also DRIFT between majors while ids do not — every DNx description gained an 'Avid ' prefix in 21.x ('DNxHR HQ' -> 'Avid DNxHR HQ 12-bit') while the ids (DNxHRHQ, DNxHRLB, DNxHRHQX_10) were unchanged. Key on ids.
|
|
474
474
|
- **Workaround / current handling:** Normalize both arguments through the live maps before calling: src.utils.render_ids.render_format_id_from_formats and render_codec_id_from_codecs accept a description or an id and return the id.
|
|
475
475
|
- **Reference:** [issue #59](https://github.com/samuelgursky/davinci-resolve-mcp/issues/59)
|
|
476
476
|
- **Tags:** render, deliver, silent-failure, id-vs-label
|
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.94.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
package/src/granular/common.py
CHANGED
|
@@ -87,7 +87,7 @@ if not logging.getLogger().handlers:
|
|
|
87
87
|
handlers=[logging.StreamHandler()],
|
|
88
88
|
)
|
|
89
89
|
|
|
90
|
-
VERSION = "2.
|
|
90
|
+
VERSION = "2.94.0"
|
|
91
91
|
logger = logging.getLogger("davinci-resolve-mcp")
|
|
92
92
|
logger.info(f"Starting DaVinci Resolve MCP Server v{VERSION}")
|
|
93
93
|
logger.info(f"Detected platform: {get_platform()}")
|
package/src/server.py
CHANGED
package/src/utils/api_truth.py
CHANGED
|
@@ -576,26 +576,58 @@ API_TRUTH: List[Dict[str, Any]] = [
|
|
|
576
576
|
"destination is the patch panel, never the lock state. In the "
|
|
577
577
|
"track header the per-track badge column is the auto-track-selector "
|
|
578
578
|
"toggle, while the source patch badge appears only on the patched "
|
|
579
|
-
"track and dragging THAT is what re-targets. So
|
|
580
|
-
"
|
|
581
|
-
"
|
|
582
|
-
"
|
|
583
|
-
"
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
"
|
|
590
|
-
"
|
|
591
|
-
"
|
|
592
|
-
"
|
|
593
|
-
"
|
|
594
|
-
"
|
|
595
|
-
"
|
|
596
|
-
"
|
|
597
|
-
"
|
|
598
|
-
"
|
|
579
|
+
"track and dragging THAT is what re-targets. So THE SELECTOR "
|
|
580
|
+
"itself is reachable only by GUI automation, and exposing "
|
|
581
|
+
"read/write on the patch panel is a smaller, better-defined API "
|
|
582
|
+
"request than adding trackIndex to all six Insert*IntoTimeline "
|
|
583
|
+
"methods. Do not read that as 'a title cannot be placed on a "
|
|
584
|
+
"chosen track from a script' — it can, without touching the "
|
|
585
|
+
"selector at all, by nesting it (see the recommendation). The gap "
|
|
586
|
+
"is in these six methods and in selector access, not in the "
|
|
587
|
+
"outcome.",
|
|
588
|
+
"recommended": "USE THE NESTED-TIMELINE ROUTE — the insert method is a dead "
|
|
589
|
+
"end but the goal is not, and this is entirely public API. "
|
|
590
|
+
"Measured end to end on Studio 21.0.4.5 (2026-08-12): put the "
|
|
591
|
+
"title on its OWN timeline (CreateEmptyTimeline + "
|
|
592
|
+
"InsertFusionTitleIntoTimeline; it lands on V1 there and that "
|
|
593
|
+
"does not matter), then place THAT timeline's media pool item "
|
|
594
|
+
"— Timeline.GetMediaPoolItem(), clip property Type='Timeline' "
|
|
595
|
+
"— with AppendToTimeline's clipInfo trackIndex/recordFrame. "
|
|
596
|
+
"It lands on the requested track at the requested frame, "
|
|
597
|
+
"exactly. The text stays settable afterwards: "
|
|
598
|
+
"placedItem.GetMediaPoolItem().GetTimeline() (Resolve 21.0.4+) "
|
|
599
|
+
"opens the inner timeline, and the Text+ item there still "
|
|
600
|
+
"reports GetFusionCompCount()==1, so "
|
|
601
|
+
"GetFusionCompByIndex(1).FindTool('Template').SetInput("
|
|
602
|
+
"'StyledText', ...) works and persists across processes. "
|
|
603
|
+
"Duration is controllable the same way: duration = endFrame - "
|
|
604
|
+
"startFrame, endFrame EXCLUSIVE (verified at 1, 119 and 120 "
|
|
605
|
+
"frames). This composes — a PNG plus two titles each placed by "
|
|
606
|
+
"trackIndex into one container timeline, and the container "
|
|
607
|
+
"placed as a single clip, all elements individually reachable "
|
|
608
|
+
"and editable through the nesting. TWO CONSTRAINTS: (a) every "
|
|
609
|
+
"placed instance shares ONE media pool item, so a text edit "
|
|
610
|
+
"propagates to all of them — use one source timeline per "
|
|
611
|
+
"distinct card; (b) placements must not overlap on a track, "
|
|
612
|
+
"or the append is silently rejected (see the overlapping-record "
|
|
613
|
+
"entry). COMPOUND CLIPS ARE THE TRAP HERE: "
|
|
614
|
+
"Timeline.CreateCompoundClip also gives a source-less title a "
|
|
615
|
+
"MediaPoolItem and also places correctly, but it SEVERS the "
|
|
616
|
+
"text — FusionCompCount drops 1->0 and GetTimeline() returns "
|
|
617
|
+
"None for Type='Compound' while working correctly for "
|
|
618
|
+
"Type='Timeline', so there is no route back to the Text+. Use "
|
|
619
|
+
"a nested timeline, never a compound, when the text must stay "
|
|
620
|
+
"editable. For clips that already have a MediaPoolItem, "
|
|
621
|
+
"AppendToTimeline clipInfo 'trackIndex' was always the answer "
|
|
622
|
+
"(exposed as media_pool append_to_timeline clip_infos). Do NOT "
|
|
623
|
+
"reach for track locking in either form — it blocks the insert "
|
|
624
|
+
"rather than re-targeting it. GUI automation of the patch panel "
|
|
625
|
+
"works but is a last resort and is unverifiable from the API "
|
|
626
|
+
"side; if used, read the landing track back with "
|
|
627
|
+
"TimelineItem.GetTrackTypeAndIndex() and treat a wrong track as "
|
|
628
|
+
"a failure. Offline, the advanced server's drp "
|
|
629
|
+
"place_fusion_title places a title by track and frame directly. "
|
|
630
|
+
"See issue #74.",
|
|
599
631
|
"tags": ["missing-method", "timeline", "title", "generator", "track"],
|
|
600
632
|
"submit": "missing",
|
|
601
633
|
"issue": 74,
|
|
@@ -1648,12 +1680,16 @@ API_TRUTH: List[Dict[str, Any]] = [
|
|
|
1648
1680
|
"SetCurrentRenderFormatAndCodec, GetRenderCodecs and "
|
|
1649
1681
|
"GetRenderResolutions all require the id, so passing the "
|
|
1650
1682
|
"description a user sees in the Deliver page is rejected. "
|
|
1651
|
-
"Verified live on Studio 19.1.3.7
|
|
1652
|
-
"
|
|
1653
|
-
"('
|
|
1654
|
-
"
|
|
1655
|
-
"obviously. Mirrors the same
|
|
1656
|
-
"returns {format: extension}."
|
|
1683
|
+
"Verified live on Studio 19.1.3.7 and re-confirmed unchanged "
|
|
1684
|
+
"on 21.0.4.5: ('mov', 'Apple ProRes 422 HQ') -> False while "
|
|
1685
|
+
"('mov', 'ProRes422HQ') -> True, and ('mp4', 'H.264') -> False "
|
|
1686
|
+
"while ('mp4', 'H264') -> True. It affects every family, not "
|
|
1687
|
+
"only the ones whose id differs obviously. Mirrors the same "
|
|
1688
|
+
"trap in GetRenderFormats, which returns {format: extension}. "
|
|
1689
|
+
"Descriptions also DRIFT between majors while ids do not — "
|
|
1690
|
+
"every DNx description gained an 'Avid ' prefix in 21.x "
|
|
1691
|
+
"('DNxHR HQ' -> 'Avid DNxHR HQ 12-bit') while the ids "
|
|
1692
|
+
"(DNxHRHQ, DNxHRLB, DNxHRHQX_10) were unchanged. Key on ids.",
|
|
1657
1693
|
"recommended": "Normalize both arguments through the live maps before "
|
|
1658
1694
|
"calling: src.utils.render_ids.render_format_id_from_formats "
|
|
1659
1695
|
"and render_codec_id_from_codecs accept a description or an "
|
|
@@ -1756,13 +1792,15 @@ API_TRUTH: List[Dict[str, Any]] = [
|
|
|
1756
1792
|
"symbol": "Project.SetCurrentRenderFormatAndCodec",
|
|
1757
1793
|
"object": "Project",
|
|
1758
1794
|
"signature": "(format, codec) -> bool",
|
|
1759
|
-
"reality": "Some render formats expose NO codecs at all
|
|
1760
|
-
"
|
|
1761
|
-
"
|
|
1762
|
-
"
|
|
1763
|
-
"
|
|
1764
|
-
"
|
|
1765
|
-
"
|
|
1795
|
+
"reality": "Some render formats expose NO codecs at all, and the call "
|
|
1796
|
+
"then rejects every codec value — the empty string, the format "
|
|
1797
|
+
"id itself, and any plausible name ('Linear PCM'). Which "
|
|
1798
|
+
"formats are affected varies by build: 'wav' and 'gif' on "
|
|
1799
|
+
"Studio 19.1.3.7; 'braw', 'mts' and 'wav' on 21.0.4.5 (gif "
|
|
1800
|
+
"gained codecs, BRAW and MTS lost them). 'wav' is affected on "
|
|
1801
|
+
"both, so there is no documented way to select it through this "
|
|
1802
|
+
"API and an audio-only WAV deliverable is not expressible in "
|
|
1803
|
+
"scripting.",
|
|
1766
1804
|
"recommended": "Check GetRenderCodecs(format) first; when it is empty, "
|
|
1767
1805
|
"treat the format as unreachable through this API rather "
|
|
1768
1806
|
"than guessing a codec value. Render audio-only via "
|
|
@@ -90,17 +90,39 @@ than emitting a spec that would be checked against an arbitrary single frame.
|
|
|
90
90
|
Every target must have one or the other — a missing QC projection is always
|
|
91
91
|
explained, never silent.
|
|
92
92
|
|
|
93
|
-
## Live-verified against the real matrix
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
2026-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
`
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
93
|
+
## Live-verified against the real matrix, twice
|
|
94
|
+
|
|
95
|
+
Checked against **19.1.3.7** (2026-07-27, 20 formats / 271 pairs) and again
|
|
96
|
+
against **21.0.4.5** (2026-08-12, 23 formats / 326 pairs).
|
|
97
|
+
|
|
98
|
+
The 19.x pass corrected guessed spellings: plain `"DNxHR HQX"` / `"DNxHR 444"`
|
|
99
|
+
never existed (the labels carry a bit depth), and DPX/TIFF codecs are spelled
|
|
100
|
+
`"RGB 10 bits"`, not `"RGB 10-bit"`.
|
|
101
|
+
|
|
102
|
+
The 21.x pass produced the more useful lesson: **codec ids were stable across the
|
|
103
|
+
two majors while descriptions were not.** Every DNx description gained an `"Avid "`
|
|
104
|
+
prefix in 21.x (`"DNxHR HQ"` -> `"Avid DNxHR HQ 12-bit"`) while the ids
|
|
105
|
+
(`DNxHRHQ`, `DNxHRLB`, `DNxHRHQX_10`) did not move. Four targets broke, and the
|
|
106
|
+
only reason the other DNx targets survived is that their candidate lists happened
|
|
107
|
+
to contain the real id. So **every codec candidate list leads with the id**, and
|
|
108
|
+
description spellings follow it — that ordering is what makes the table survive a
|
|
109
|
+
major-version upgrade.
|
|
110
|
+
|
|
111
|
+
Two findings changed between the builds and should not be treated as fixed facts:
|
|
112
|
+
|
|
113
|
+
- **PNG, WebP and HLS.** Not render formats on 19.1.3.7; they *are* on 21.0.4.5.
|
|
114
|
+
`png_sequence`, `webp_animated` and `hls_h264` therefore resolve on 21.x and
|
|
115
|
+
fail loudly on 19.x, which is the correct behavior rather than a regression.
|
|
116
|
+
PNG exposes RGB only — no alpha codec — so it does not replace `dpx_sequence`
|
|
117
|
+
for transparency.
|
|
118
|
+
- **Zero-codec formats.** `wav` and `gif` on 19.x; `braw`, `mts` and `wav` on
|
|
119
|
+
21.x — `gif` gained codecs, BRAW and MTS lost them. `wav` is the constant, so
|
|
120
|
+
an audio-only WAV target remains inexpressible through
|
|
121
|
+
`SetCurrentRenderFormatAndCodec`, which rejects every value including the
|
|
122
|
+
empty string.
|
|
123
|
+
|
|
124
|
+
Availability varies by version, license, and installed IO plugins, so resolution
|
|
125
|
+
stays live and neither count above is a target to compare against.
|
|
104
126
|
|
|
105
127
|
## ffprobe gotcha encoded here
|
|
106
128
|
|
|
@@ -135,7 +157,9 @@ TIERS = ("master", "web", "sequence", "broadcast", "package")
|
|
|
135
157
|
|
|
136
158
|
#: What the shipped candidates were checked against. Availability is still
|
|
137
159
|
#: machine/license/plugin dependent, so resolution happens live regardless.
|
|
138
|
-
VERIFIED_ON =
|
|
160
|
+
VERIFIED_ON = (
|
|
161
|
+
"DaVinci Resolve Studio 21.0.4.5 (2026-08-12, 23 formats / 326 pairs); first verified on 19.1.3.7 (2026-07-27, 20 formats / 271 pairs)"
|
|
162
|
+
)
|
|
139
163
|
|
|
140
164
|
|
|
141
165
|
# ── Loudness standards ──────────────────────────────────────────────────────
|
|
@@ -556,6 +580,63 @@ DELIVERY_TARGETS: Dict[str, DeliveryTarget] = {
|
|
|
556
580
|
("RGB 16 bits", "RGB16", "RGB 8 bits"),
|
|
557
581
|
"TIFF frames for stills-oriented or archival handoff.",
|
|
558
582
|
),
|
|
583
|
+
"png_sequence": _sequence(
|
|
584
|
+
"png_sequence", "PNG image sequence", ("PNG", "png"),
|
|
585
|
+
("RGB16", "RGB 16 bits", "RGB8", "RGB 8 bits"),
|
|
586
|
+
"PNG frames for web and motion-graphics handoff.",
|
|
587
|
+
notes=(
|
|
588
|
+
"RGB ONLY — Resolve exposes no alpha PNG codec (21.0.4.5 offers just "
|
|
589
|
+
"'RGB 16 bits' / 'RGB 8 bits'). For transparency use dpx_sequence "
|
|
590
|
+
"('RGBA 8 bits') or prores4444_master.",
|
|
591
|
+
"PNG is not a render format on Resolve 19.x; this target resolves on 21.x+.",
|
|
592
|
+
),
|
|
593
|
+
),
|
|
594
|
+
# ── Animated web assets ─────────────────────────────────────────────────
|
|
595
|
+
# Not _web(): that helper pins mp4 and requires a raster. These carry no
|
|
596
|
+
# audio track at all, so export_audio is False rather than merely unpinned.
|
|
597
|
+
"gif_animated": DeliveryTarget(
|
|
598
|
+
id="gif_animated",
|
|
599
|
+
label="Animated GIF",
|
|
600
|
+
describe="Looping animated GIF for web/social placement. No audio.",
|
|
601
|
+
tier="web",
|
|
602
|
+
format_candidates=("GIF", "gif"),
|
|
603
|
+
codec_candidates=("Animated_GIF", "Animated GIF"),
|
|
604
|
+
qc_container="gif",
|
|
605
|
+
qc_codec="gif",
|
|
606
|
+
export_audio=False,
|
|
607
|
+
verified=_VERIFIED,
|
|
608
|
+
source="Live matrix: GIF render format; ffprobe values measured on a generated .gif.",
|
|
609
|
+
notes=(
|
|
610
|
+
"GIF carries no audio and is palette-limited to 256 colours; prefer "
|
|
611
|
+
"webp_animated where the destination supports it.",
|
|
612
|
+
"Raster and rate inherit the timeline — set them explicitly for a "
|
|
613
|
+
"placement with a fixed spec.",
|
|
614
|
+
),
|
|
615
|
+
),
|
|
616
|
+
"webp_animated": DeliveryTarget(
|
|
617
|
+
id="webp_animated",
|
|
618
|
+
label="Animated WebP",
|
|
619
|
+
describe="Looping animated WebP for web placement. No audio.",
|
|
620
|
+
tier="web",
|
|
621
|
+
format_candidates=("WebP", "webp"),
|
|
622
|
+
codec_candidates=("Animated_WEBP", "Animated WebP"),
|
|
623
|
+
export_audio=False,
|
|
624
|
+
# No QC projection on purpose — see qc_skip_reason. Guessing "webp"/"webp"
|
|
625
|
+
# would produce failures that say nothing about the deliverable, which is
|
|
626
|
+
# the exact trap the mp4-reports-as-mov case taught.
|
|
627
|
+
qc_skip_reason=(
|
|
628
|
+
"ffprobe container/codec values for animated WebP are unverified: the "
|
|
629
|
+
"reference machine's ffmpeg has only the webp_pipe still-image demuxer, "
|
|
630
|
+
"so a rendered animated WebP could not be probed. Measure and fill in "
|
|
631
|
+
"qc_container/qc_codec on a build with full WebP support."
|
|
632
|
+
),
|
|
633
|
+
verified=_VERIFIED,
|
|
634
|
+
source="Live matrix: WebP render format (ffprobe side unmeasured).",
|
|
635
|
+
notes=(
|
|
636
|
+
"WebP carries no audio track.",
|
|
637
|
+
"WebP is not a render format on Resolve 19.x; this target resolves on 21.x+.",
|
|
638
|
+
),
|
|
639
|
+
),
|
|
559
640
|
# ── Broadcast / Avid handoff ────────────────────────────────────────────
|
|
560
641
|
"dnxhr_hq_mxf_opatom": DeliveryTarget(
|
|
561
642
|
id="dnxhr_hq_mxf_opatom",
|
|
@@ -622,6 +703,17 @@ DELIVERY_TARGETS: Dict[str, DeliveryTarget] = {
|
|
|
622
703
|
"need a human. easyDCP variants exist on this install too.",
|
|
623
704
|
),
|
|
624
705
|
),
|
|
706
|
+
"hls_h264": _package(
|
|
707
|
+
"hls_h264", "HLS package (H.264)",
|
|
708
|
+
("HLS", "m3u8"), ("H264", "H.264"),
|
|
709
|
+
"HTTP Live Streaming package: an .m3u8 playlist plus its segment files.",
|
|
710
|
+
notes=(
|
|
711
|
+
"HLS is a PACKAGE: a playlist plus segments, not one file. Bitrate ladders, "
|
|
712
|
+
"variant playlists and encryption keys are not expressible as a render target "
|
|
713
|
+
"— this selects the format/codec only.",
|
|
714
|
+
"HLS is not a render format on Resolve 19.x; this target resolves on 21.x+.",
|
|
715
|
+
),
|
|
716
|
+
),
|
|
625
717
|
}
|
|
626
718
|
|
|
627
719
|
VALID_TARGETS = frozenset(DELIVERY_TARGETS)
|
|
@@ -654,6 +746,11 @@ TARGET_ALIASES: Dict[str, str] = {
|
|
|
654
746
|
"dnx_master": "dnxhr_hqx_master",
|
|
655
747
|
"avid": "dnxhr_hq_mxf_opatom",
|
|
656
748
|
"vfx": "exr_sequence",
|
|
749
|
+
"png": "png_sequence",
|
|
750
|
+
"gif": "gif_animated",
|
|
751
|
+
"webp": "webp_animated",
|
|
752
|
+
"hls": "hls_h264",
|
|
753
|
+
"streaming": "hls_h264",
|
|
657
754
|
}
|
|
658
755
|
|
|
659
756
|
#: Fields a caller may override per call. Deliberately excludes id/label/tier/
|