Skip to content

Commit c6c06d2

Browse files
committed
fix(delivery): withdraw hls_h264 and report each target's own QC reason
hls_h264 shipped in 2.94.0 because it resolved cleanly against the format/codec matrix. It can never render. On Studio 21.0.4.5 GetRenderCodecs('m3u8') returns {'H.264': 'H264'} and GetRenderResolutions('m3u8','H264') returns real rasters, yet SetCurrentRenderFormatAndCodec('m3u8', ...) is False for every value tried ('H264', 'H.264', 'h264', '') while ('mp4','H264') succeeds. Withdrawn along with its hls and streaming aliases; a target that always fails is worse than no target. Only queuing an actual job caught this. Resolving a name proves the pair is LISTED, not that it is usable — so the matrix is not a capability contract. Folded into the existing Project.GetRenderCodecs api_truth entry rather than added as a second entry for the same symbol. Also fixes qc_note, which hard-coded an image-sequence message for every target without a QC projection. webp_animated declines QC because its ffprobe values are unmeasured, and was telling callers its output was a many-file sequence. It now surfaces the target's own qc_skip_reason. 31 targets, each verified end to end on 21.0.4.5: resolved, queued as a real render job, then deleted, with project render state restored. Python 2615 tests pass. Node is untouched by this change (zero files under resolve-advanced) and was green at 553 tests on identical code.
1 parent 96544da commit c6c06d2

11 files changed

Lines changed: 97 additions & 31 deletions

File tree

CHANGELOG.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,39 @@
22

33
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.
44

5+
## What's New in v2.94.1
6+
7+
Removes the `hls_h264` target added in v2.94.0 — it could never work — and fixes
8+
a QC note that told the truth only for image sequences.
9+
10+
### Fixed
11+
12+
- **`hls_h264` is withdrawn.** It resolved cleanly against the format/codec
13+
matrix, which is why it shipped, but it can never render: on Studio 21.0.4.5
14+
`GetRenderCodecs('m3u8')` returns `{'H.264': 'H264'}` and
15+
`GetRenderResolutions('m3u8', 'H264')` returns real rasters, yet
16+
`SetCurrentRenderFormatAndCodec('m3u8', ...)` is False for every value tried
17+
(`'H264'`, `'H.264'`, `'h264'`, `''`) while `('mp4', 'H264')` succeeds. Caught
18+
by queuing an actual job rather than by resolving a name. A target that always
19+
fails is worse than no target, so it is gone along with its `hls` and
20+
`streaming` aliases.
21+
- **`qc_note` reported every target's missing QC projection as an image
22+
sequence.** It was hard-coded, so `webp_animated` — which declines QC because
23+
its ffprobe values are unmeasured — told callers its output was a many-file
24+
sequence. It now surfaces the target's own `qc_skip_reason`.
25+
26+
### Notes
27+
28+
- New API-truth finding, folded into the existing `Project.GetRenderCodecs`
29+
entry: **the format/codec matrix is not a capability contract.** A format can
30+
advertise a codec, and rasters for it, and still refuse to be selected. That is
31+
worse than the zero-codec formats, which at least advertise nothing. Presence
32+
in the matrix proves a pair is *listed*, never that it is usable — the
33+
authoritative test is setting it and reading the boolean back, which
34+
`prepare_render_job` and `prepare_delivery_job` already do.
35+
- 31 targets, all verified end to end on Studio 21.0.4.5: resolved, queued as a
36+
real render job, then deleted.
37+
538
## What's New in v2.94.0
639

740
Adds four delivery targets for deliverable classes the table did not cover:

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
English | [简体中文](README.zh-CN.md)
44

5-
[![Version](https://img.shields.io/badge/version-2.94.0-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5+
[![Version](https://img.shields.io/badge/version-2.94.1-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
66
[![npm](https://img.shields.io/npm/v/davinci-resolve-mcp.svg?label=npm&color=CB3837)](https://www.npmjs.com/package/davinci-resolve-mcp)
77
[![API Coverage](https://img.shields.io/badge/API%20Coverage-100%25-brightgreen.svg)](docs/reference/api-coverage.md)
88
[![Tools](https://img.shields.io/badge/MCP%20Tools-34%20(353%20full)-blue.svg)](#server-modes)

README.zh-CN.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
[English](README.md) | 简体中文
44

5-
[![Version](https://img.shields.io/badge/version-2.94.0-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5+
[![Version](https://img.shields.io/badge/version-2.94.1-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
66
[![npm](https://img.shields.io/npm/v/davinci-resolve-mcp.svg?label=npm&color=CB3837)](https://www.npmjs.com/package/davinci-resolve-mcp)
77
[![API Coverage](https://img.shields.io/badge/API%20Coverage-100%25-brightgreen.svg)](docs/reference/api-coverage.md)
88
[![Tools](https://img.shields.io/badge/MCP%20Tools-34%20(353%20full)-blue.svg)](#服务器模式)
@@ -12,7 +12,7 @@
1212
[![Python](https://img.shields.io/badge/python-3.10+-green.svg)](https://www.python.org/downloads/)
1313
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT)
1414

15-
> 本翻译对应 v2.94.0 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
15+
> 本翻译对应 v2.94.1 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
1616
1717
一个 Model Context Protocol (MCP) 服务器,让 AI 助手通过官方脚本 API 控制 DaVinci Resolve Studio(达芬奇)。它提供完整的 API 覆盖,外加带护栏的工作流助手,涵盖剪辑、媒体池整理、渲染设置、审阅标记、调色、Fusion、Fairlight、项目生命周期任务、扩展开发,以及不碰源媒体的媒体分析。
1818

docs/reference/api-limitations.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -470,8 +470,8 @@ values, or automation-hostile modal prompts.
470470

471471
- **Object:** `Project`
472472
- **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 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-
- **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.
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. SEPARATELY, the returned map is not a capability contract: a format can ADVERTISE a codec it will not accept. On 21.0.4.5 GetRenderCodecs('m3u8') returns {'H.264': 'H264'} and GetRenderResolutions('m3u8','H264') returns real rasters, yet SetCurrentRenderFormatAndCodec('m3u8', ...) is False for every value tried ('H264', 'H.264', 'h264', '') while ('mp4','H264') succeeds. That is worse than the zero-codec formats, which at least advertise nothing.
474+
- **Workaround / current handling:** Never treat presence in the matrix as proof a pair is usable — set it and read the boolean back (prepare_render_job / prepare_delivery_job do this and refuse to queue on False). 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.
475475
- **Reference:** [issue #59](https://github.com/samuelgursky/davinci-resolve-mcp/issues/59)
476476
- **Tags:** render, deliver, silent-failure, id-vs-label
477477

install.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636

3737
# ─── Version ──────────────────────────────────────────────────────────────────
3838

39-
VERSION = "2.94.0"
39+
VERSION = "2.94.1"
4040
# Only hard floor: mcp[cli] requires Python 3.10+. There is no upper bound —
4141
# Resolve's scripting bridge loads into newer interpreters on recent builds
4242
# (Python 3.14 verified against Resolve Studio 20.3.2). Older Resolve builds

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "davinci-resolve-mcp",
3-
"version": "2.94.0",
3+
"version": "2.94.1",
44
"description": "NPM bootstrapper for the DaVinci Resolve MCP Server.",
55
"license": "MIT",
66
"author": "Samuel Gursky <samgursky@gmail.com>",

src/granular/common.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@
8787
handlers=[logging.StreamHandler()],
8888
)
8989

90-
VERSION = "2.94.0"
90+
VERSION = "2.94.1"
9191
logger = logging.getLogger("davinci-resolve-mcp")
9292
logger.info(f"Starting DaVinci Resolve MCP Server v{VERSION}")
9393
logger.info(f"Detected platform: {get_platform()}")

src/server.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
python src/server.py --full # Start the 353-tool granular server instead
1212
"""
1313

14-
VERSION = "2.94.0"
14+
VERSION = "2.94.1"
1515

1616
import base64
1717
import os
@@ -17007,10 +17007,16 @@ def _resolve_delivery_target_live(proj, p: Dict[str, Any]):
1700717007
"timeline_fps": fps,
1700817008
"settings": _delivery_targets.to_render_settings(target, timeline_fps=fps),
1700917009
"qc_spec": qc_spec,
17010+
# Surface the target's OWN reason. This used to hard-code an
17011+
# image-sequence message, which was simply false for any other kind
17012+
# of target that declines a QC projection.
1701017013
"qc_note": (
1701117014
None
1701217015
if qc_spec
17013-
else "This target renders an image sequence; deliverable_qc probes a single file, so it has no QC spec."
17016+
else (
17017+
target.qc_skip_reason
17018+
or "This target has no single-file QC projection."
17019+
)
1701417020
),
1701517021
"loudness_target": loudness,
1701617022
"loudness_note": (

src/utils/api_truth.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1689,8 +1689,20 @@
16891689
"Descriptions also DRIFT between majors while ids do not — "
16901690
"every DNx description gained an 'Avid ' prefix in 21.x "
16911691
"('DNxHR HQ' -> 'Avid DNxHR HQ 12-bit') while the ids "
1692-
"(DNxHRHQ, DNxHRLB, DNxHRHQX_10) were unchanged. Key on ids.",
1693-
"recommended": "Normalize both arguments through the live maps before "
1692+
"(DNxHRHQ, DNxHRLB, DNxHRHQX_10) were unchanged. Key on ids. "
1693+
"SEPARATELY, the returned map is not a capability contract: a "
1694+
"format can ADVERTISE a codec it will not accept. On 21.0.4.5 "
1695+
"GetRenderCodecs('m3u8') returns {'H.264': 'H264'} and "
1696+
"GetRenderResolutions('m3u8','H264') returns real rasters, yet "
1697+
"SetCurrentRenderFormatAndCodec('m3u8', ...) is False for every "
1698+
"value tried ('H264', 'H.264', 'h264', '') while ('mp4','H264') "
1699+
"succeeds. That is worse than the zero-codec formats, which at "
1700+
"least advertise nothing.",
1701+
"recommended": "Never treat presence in the matrix as proof a pair is "
1702+
"usable — set it and read the boolean back "
1703+
"(prepare_render_job / prepare_delivery_job do this and "
1704+
"refuse to queue on False). Normalize both arguments "
1705+
"through the live maps before "
16941706
"calling: src.utils.render_ids.render_format_id_from_formats "
16951707
"and render_codec_id_from_codecs accept a description or an "
16961708
"id and return the id.",

src/utils/delivery_targets.py

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,15 @@
110110
111111
Two findings changed between the builds and should not be treated as fixed facts:
112112
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.
113+
- **PNG and WebP.** Not render formats on 19.1.3.7; they *are* on 21.0.4.5, so
114+
`png_sequence` and `webp_animated` resolve on 21.x and fail loudly on 19.x,
115+
which is correct behavior rather than a regression. PNG exposes RGB only — no
116+
alpha codec — so it does not replace `dpx_sequence` for transparency.
117+
- **A format can advertise a codec it will not accept.** `HLS` (m3u8) returns a
118+
codec from `GetRenderCodecs` and real rasters from `GetRenderResolutions`, yet
119+
`SetCurrentRenderFormatAndCodec` rejects every value. Resolving a target proves
120+
only that the pair is *listed*; the authoritative test is queuing a job, which
121+
is why `prepare_delivery_job` refuses to queue on a rejected pair.
118122
- **Zero-codec formats.** `wav` and `gif` on 19.x; `braw`, `mts` and `wav` on
119123
21.x — `gif` gained codecs, BRAW and MTS lost them. `wav` is the constant, so
120124
an audio-only WAV target remains inexpressible through
@@ -703,17 +707,13 @@ def _package(key: str, label: str, formats: Tuple[str, ...], codecs: Tuple[str,
703707
"need a human. easyDCP variants exist on this install too.",
704708
),
705709
),
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-
),
710+
# No HLS target. `HLS` (m3u8) looks fully supported through the query APIs on
711+
# 21.0.4.5 — GetRenderCodecs returns {'H.264': 'H264'} and GetRenderResolutions
712+
# returns real rasters — but SetCurrentRenderFormatAndCodec('m3u8', ...) is
713+
# rejected for every value tried ('H264', 'H.264', 'h264', ''), while the same
714+
# call for mp4/H264 succeeds. The matrix advertises a pair that cannot be
715+
# selected, so a target for it would fail 100% of the time.
716+
# Verified on Studio 21.0.4.5, 2026-08-12. See api_truth.py.
717717
}
718718

719719
VALID_TARGETS = frozenset(DELIVERY_TARGETS)
@@ -749,8 +749,6 @@ def _package(key: str, label: str, formats: Tuple[str, ...], codecs: Tuple[str,
749749
"png": "png_sequence",
750750
"gif": "gif_animated",
751751
"webp": "webp_animated",
752-
"hls": "hls_h264",
753-
"streaming": "hls_h264",
754752
}
755753

756754
#: Fields a caller may override per call. Deliberately excludes id/label/tier/

0 commit comments

Comments
 (0)