davinci-resolve-mcp 4.17.0 → 4.17.1
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 +27 -0
- package/README.md +1 -1
- package/README.zh-CN.md +2 -2
- 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/media_analysis.py +42 -18
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,33 @@
|
|
|
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.17.1 — Media analysis decodes each file once
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- **The readthrough pass of media analysis runs ffmpeg once instead of four
|
|
10
|
+
times** (#329, thanks @Dev-next-gen). Loudness, scene scoring, black frames
|
|
11
|
+
and silence each used to be a separate ffmpeg run, and each run decoded the
|
|
12
|
+
whole file: `-af` alone does not drop the video stream from a null output,
|
|
13
|
+
so the audio passes decoded every video frame too. The four filters now
|
|
14
|
+
share one run through `split`/`asplit`. On a 1080p H.264 file the
|
|
15
|
+
readthrough took 8.1 s instead of 22.1 s, and CPU time fell from 84.8 s to
|
|
16
|
+
30.5 s; ProRes and HEVC dropped by about the same share. The results are
|
|
17
|
+
the same as before. The parsers are unchanged.
|
|
18
|
+
- The four measurements now share one exit code and one timeout. The single
|
|
19
|
+
run gets four times the old per-pass timeout, the budget of the four runs it
|
|
20
|
+
replaces. If that run times out, all four report `success: false`, where
|
|
21
|
+
before the other three could still succeed.
|
|
22
|
+
|
|
23
|
+
### Validation
|
|
24
|
+
|
|
25
|
+
- Python suite with the PR merged on `main`: 4,291 tests. The only failures
|
|
26
|
+
are the 4 `source_media_guard` path checks that always fail in a scratch
|
|
27
|
+
worktree. The new test compares the single run against the four separate
|
|
28
|
+
runs with the real ffmpeg on an A/V file, a video-only file and an
|
|
29
|
+
audio-only file.
|
|
30
|
+
- No Resolve behaviour changed; this is ffmpeg only.
|
|
31
|
+
|
|
5
32
|
## What's New in v4.17.0 — Power windows for Resolve 21
|
|
6
33
|
|
|
7
34
|
### Added
|
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.17.
|
|
15
|
+
> 本翻译对应 v4.17.1 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
|
|
16
16
|
|
|
17
17
|
一个 Model Context Protocol (MCP) 服务器,让 AI 助手通过官方脚本 API 控制 DaVinci Resolve Studio(达芬奇)。它提供完整的 API 覆盖,外加带护栏的工作流助手,涵盖剪辑、媒体池整理、渲染设置、审阅标记、调色、Fusion、Fairlight、项目生命周期任务、扩展开发,以及不碰源媒体的媒体分析。
|
|
18
18
|
|
package/install.py
CHANGED
|
@@ -37,7 +37,7 @@ from src.utils.update_check import (
|
|
|
37
37
|
|
|
38
38
|
# ─── Version ──────────────────────────────────────────────────────────────────
|
|
39
39
|
|
|
40
|
-
VERSION = "4.17.
|
|
40
|
+
VERSION = "4.17.1"
|
|
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
package/src/granular/common.py
CHANGED
|
@@ -92,7 +92,7 @@ if not logging.getLogger().handlers:
|
|
|
92
92
|
handlers=[logging.StreamHandler()],
|
|
93
93
|
)
|
|
94
94
|
|
|
95
|
-
VERSION = "4.17.
|
|
95
|
+
VERSION = "4.17.1"
|
|
96
96
|
logger = logging.getLogger("davinci-resolve-mcp")
|
|
97
97
|
logger.info(f"Starting DaVinci Resolve MCP Server v{VERSION}")
|
|
98
98
|
logger.info(f"Detected platform: {get_platform()}")
|
package/src/server.py
CHANGED
|
@@ -2781,7 +2781,13 @@ def _media_duration_seconds(record: Dict[str, Any], technical: Dict[str, Any]) -
|
|
|
2781
2781
|
return None
|
|
2782
2782
|
|
|
2783
2783
|
|
|
2784
|
-
def _ffmpeg_stderr_filter(
|
|
2784
|
+
def _ffmpeg_stderr_filter(
|
|
2785
|
+
path: str,
|
|
2786
|
+
video_filter: Optional[str] = None,
|
|
2787
|
+
audio_filter: Optional[str] = None,
|
|
2788
|
+
frames: Optional[int] = None,
|
|
2789
|
+
timeout: int = COMMAND_TIMEOUT_SECONDS,
|
|
2790
|
+
) -> Tuple[int, str]:
|
|
2785
2791
|
args = ["ffmpeg", "-hide_banner", "-nostats", "-i", path]
|
|
2786
2792
|
if video_filter:
|
|
2787
2793
|
args.extend(["-vf", video_filter])
|
|
@@ -2790,7 +2796,7 @@ def _ffmpeg_stderr_filter(path: str, video_filter: Optional[str] = None, audio_f
|
|
|
2790
2796
|
if frames is not None:
|
|
2791
2797
|
args.extend(["-frames:v", str(frames)])
|
|
2792
2798
|
args.extend(["-f", "null", "-"])
|
|
2793
|
-
code, _, stderr = _run_command(args)
|
|
2799
|
+
code, _, stderr = _run_command(args, timeout=timeout)
|
|
2794
2800
|
return code, stderr
|
|
2795
2801
|
|
|
2796
2802
|
|
|
@@ -2934,25 +2940,45 @@ def _parse_idet(stderr: str) -> Dict[str, Any]:
|
|
|
2934
2940
|
}
|
|
2935
2941
|
|
|
2936
2942
|
|
|
2943
|
+
# The four whole-file measurements `_readthrough_analysis` makes. They used to be four
|
|
2944
|
+
# ffmpeg runs, and every run decoded the whole file: `-af` alone does not drop the video
|
|
2945
|
+
# stream from a null output, so the loudness and silence passes decoded every frame just
|
|
2946
|
+
# as the scene and black passes did, and each video pass decoded the audio too. One run
|
|
2947
|
+
# with `split`/`asplit` decodes each stream once and feeds all four filters. Each parser
|
|
2948
|
+
# below matches only its own filter's lines, so sharing one stderr changes no result.
|
|
2949
|
+
# A missing stream behaves as it did per pass: ffmpeg skips the -vf or -af it cannot
|
|
2950
|
+
# use, exits 0, and that filter's parser finds nothing.
|
|
2951
|
+
_SCENE_SCORE_FILTER = "select='gt(scene,0)',metadata=print:key=lavfi.scene_score,showinfo"
|
|
2952
|
+
_BLACKDETECT_FILTER = "blackdetect=d=0.5:pix_th=0.10"
|
|
2953
|
+
_LOUDNESS_FILTER = "ebur128=peak=true"
|
|
2954
|
+
_SILENCEDETECT_FILTER = "silencedetect=noise=-50dB:d=1"
|
|
2955
|
+
_READTHROUGH_VIDEO_FILTER = f"split[scene][black];[scene]{_SCENE_SCORE_FILTER},nullsink;[black]{_BLACKDETECT_FILTER}"
|
|
2956
|
+
_READTHROUGH_AUDIO_FILTER = f"asplit[loud][silence];[loud]{_LOUDNESS_FILTER},anullsink;[silence]{_SILENCEDETECT_FILTER}"
|
|
2957
|
+
|
|
2958
|
+
|
|
2937
2959
|
def _readthrough_analysis(path: str) -> Dict[str, Any]:
|
|
2938
2960
|
result: Dict[str, Any] = {"success": True}
|
|
2939
2961
|
|
|
2940
|
-
|
|
2962
|
+
# The one run carries the timeout budget of the four it replaces, so a file whose
|
|
2963
|
+
# slowest pass (scene scoring) finished in time before still finishes in time.
|
|
2964
|
+
code, stderr = _ffmpeg_stderr_filter(
|
|
2965
|
+
path,
|
|
2966
|
+
video_filter=_READTHROUGH_VIDEO_FILTER,
|
|
2967
|
+
audio_filter=_READTHROUGH_AUDIO_FILTER,
|
|
2968
|
+
timeout=COMMAND_TIMEOUT_SECONDS * 4,
|
|
2969
|
+
)
|
|
2970
|
+
|
|
2941
2971
|
result["loudness"] = {
|
|
2942
|
-
"success":
|
|
2943
|
-
"metrics": _parse_loudness(
|
|
2972
|
+
"success": code == 0,
|
|
2973
|
+
"metrics": _parse_loudness(stderr),
|
|
2944
2974
|
}
|
|
2945
2975
|
|
|
2946
|
-
# Adaptive scene detection.
|
|
2976
|
+
# Adaptive scene detection. The scene branch dumps a (pts_time, scene_score) pair
|
|
2947
2977
|
# for every frame whose scene score is > 0 (i.e. every non-first frame); we then
|
|
2948
2978
|
# compute a content-aware threshold from that distribution and keep peaks above it.
|
|
2949
2979
|
# Replaces the legacy hardcoded ``gt(scene,0.3)``, which was too coarse on
|
|
2950
2980
|
# high-motion content (missed real cuts) and too sensitive on locked-off content.
|
|
2951
|
-
|
|
2952
|
-
path,
|
|
2953
|
-
video_filter="select='gt(scene,0)',metadata=print:key=lavfi.scene_score,showinfo",
|
|
2954
|
-
)
|
|
2955
|
-
scene_score_pairs = _parse_scene_score_pairs(scene_stderr)
|
|
2981
|
+
scene_score_pairs = _parse_scene_score_pairs(stderr)
|
|
2956
2982
|
scene_threshold, scene_threshold_stats = _adaptive_scene_threshold(scene_score_pairs)
|
|
2957
2983
|
adaptive_scene_items = [
|
|
2958
2984
|
{"time_seconds": pts, "score": score}
|
|
@@ -2960,22 +2986,20 @@ def _readthrough_analysis(path: str) -> Dict[str, Any]:
|
|
|
2960
2986
|
if score is not None and score > scene_threshold
|
|
2961
2987
|
]
|
|
2962
2988
|
result["scenes"] = {
|
|
2963
|
-
"success":
|
|
2989
|
+
"success": code == 0,
|
|
2964
2990
|
"items": adaptive_scene_items,
|
|
2965
2991
|
"threshold": scene_threshold,
|
|
2966
2992
|
"threshold_stats": scene_threshold_stats,
|
|
2967
2993
|
}
|
|
2968
2994
|
|
|
2969
|
-
black_code, black_stderr = _ffmpeg_stderr_filter(path, video_filter="blackdetect=d=0.5:pix_th=0.10")
|
|
2970
2995
|
result["black_frames"] = {
|
|
2971
|
-
"success":
|
|
2972
|
-
"items": _parse_blackdetect(
|
|
2996
|
+
"success": code == 0,
|
|
2997
|
+
"items": _parse_blackdetect(stderr),
|
|
2973
2998
|
}
|
|
2974
2999
|
|
|
2975
|
-
silence_code, silence_stderr = _ffmpeg_stderr_filter(path, audio_filter="silencedetect=noise=-50dB:d=1")
|
|
2976
3000
|
result["silence"] = {
|
|
2977
|
-
"success":
|
|
2978
|
-
"items": _parse_silencedetect(
|
|
3001
|
+
"success": code == 0,
|
|
3002
|
+
"items": _parse_silencedetect(stderr),
|
|
2979
3003
|
}
|
|
2980
3004
|
|
|
2981
3005
|
idet_code, idet_stderr = _ffmpeg_stderr_filter(path, video_filter="idet", frames=500)
|