davinci-resolve-mcp 2.68.1 → 2.69.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 +213 -0
- package/README.md +46 -3
- package/docs/SKILL.md +181 -1
- package/docs/install.md +27 -1
- package/install.py +26 -1
- package/package.json +1 -1
- package/resolve-advanced/LICENSE +21 -0
- package/resolve-advanced/package.json +2 -1
- package/resolve-advanced/vendor/conform-qc/package.json +1 -0
- package/resolve-advanced/vendor/drp-format/package.json +1 -0
- package/resolve-advanced/vendor/drt-format/package.json +1 -0
- package/scripts/bridge_differential.py +22 -4
- package/scripts/doctor.py +129 -3
- package/scripts/install_resolve_bridge.py +13 -3
- package/src/granular/common.py +1 -1
- package/src/server.py +639 -18
- package/src/utils/beat_detection.py +242 -0
- package/src/utils/bridge_differential.py +27 -1
- package/src/utils/broll_placement.py +201 -0
- package/src/utils/conform_lint.py +437 -0
- package/src/utils/edit_engine.py +633 -1
- package/src/utils/edit_handles.py +206 -0
- package/src/utils/edit_report.py +360 -0
- package/src/utils/first_impression.py +212 -0
- package/src/utils/prebalance.py +471 -0
- package/src/utils/project_journal.py +360 -0
- package/src/utils/reference_match.py +203 -0
- package/src/utils/rhythm_audit.py +252 -0
- package/src/utils/rule_of_six.py +217 -0
- package/src/utils/setup_sheet.py +121 -0
- package/src/utils/shot_assembly.py +259 -0
- package/src/utils/silence_ripple.py +89 -2
- package/src/utils/sound_density.py +253 -0
- package/src/utils/split_edits.py +180 -0
- package/src/utils/take_ranking.py +206 -0
- package/src/utils/transcript_edit.py +101 -5
- package/src/utils/turnover.py +187 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"""Turnover packages — what leaves the cutting room, and what it must contain.
|
|
2
|
+
|
|
3
|
+
A turnover is the moment the edit stops being one person's problem. Sound, VFX
|
|
4
|
+
and colour each need a different package, each has a specification that has been
|
|
5
|
+
stable for decades, and each fails in the same way: something obvious is
|
|
6
|
+
missing, nobody notices until the receiving facility has started, and the fix
|
|
7
|
+
costs a day of somebody else's schedule.
|
|
8
|
+
|
|
9
|
+
The specifications are not subtle. Sound wants an AAF with generous handles and
|
|
10
|
+
a picture reference with burned-in timecode. VFX wants plates with handles, a
|
|
11
|
+
burn, and a shot list. Colour wants the conform, the camera-original paths, and
|
|
12
|
+
whatever CDL or LUT the look was built on. All of it is checkable in advance.
|
|
13
|
+
|
|
14
|
+
## Manifests, not exports
|
|
15
|
+
|
|
16
|
+
This builds and validates the **manifest** — what belongs in the package, what
|
|
17
|
+
the handle requirement is, what is missing. It does not render or export;
|
|
18
|
+
that needs a live Resolve, and pretending otherwise would produce a package
|
|
19
|
+
nobody could deliver. The manifest is the useful half anyway: an export that
|
|
20
|
+
runs perfectly and omits the textless is still a failed turnover.
|
|
21
|
+
|
|
22
|
+
## The advice this exists to enforce
|
|
23
|
+
|
|
24
|
+
The single loudest instruction in the online-editing craft is about the picture
|
|
25
|
+
reference: *never, ever, EVER forget to send an output of your picture-locked
|
|
26
|
+
sequence with timecode burn.* Without it the receiving editor has no way to
|
|
27
|
+
verify anything against your intent. It is required in every package type here,
|
|
28
|
+
and its absence is a **blocker**, not a warning.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
from typing import Any, Dict, Mapping, Optional, Sequence
|
|
34
|
+
|
|
35
|
+
#: Handle requirements by destination, in frames. Sound is far larger because
|
|
36
|
+
#: crossfades, room tone and dialogue overlap all reach past the picture cut.
|
|
37
|
+
HANDLE_REQUIREMENTS = {
|
|
38
|
+
"sound": {"frames": 48, "note": "48-120 frames; crossfades and room tone reach past the cut"},
|
|
39
|
+
"vfx": {"frames": 8, "note": "8-24 frames; tracking needs to run in and out of the shot"},
|
|
40
|
+
"color": {"frames": 8, "note": "8-24 frames; room to slip a shot and to dissolve"},
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
#: What each package must contain. `required` absences are blockers.
|
|
44
|
+
PACKAGE_SPECS: Dict[str, Dict[str, Any]] = {
|
|
45
|
+
"sound": {
|
|
46
|
+
"required": ["aaf", "picture_reference_with_burn", "handles"],
|
|
47
|
+
"recommended": ["session_notes", "adr_list", "temp_music_notes"],
|
|
48
|
+
"audio": {"sample_rate": "48kHz", "bit_depth": 24, "channels": "split stereo to dual mono"},
|
|
49
|
+
},
|
|
50
|
+
"vfx": {
|
|
51
|
+
"required": ["plates", "picture_reference_with_burn", "shot_list", "handles"],
|
|
52
|
+
"recommended": ["clean_plates", "roto_reference", "camera_metadata", "reference_grade"],
|
|
53
|
+
},
|
|
54
|
+
"color": {
|
|
55
|
+
"required": ["conform", "camera_original_paths", "picture_reference_with_burn"],
|
|
56
|
+
"recommended": ["cdl", "show_lut", "dailies_lut", "scene_notes"],
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
#: Burn-in fields for the picture reference. Source timecode and record timecode
|
|
61
|
+
#: are both required: one identifies the material, the other identifies the
|
|
62
|
+
#: position in the cut, and a reference carrying only one of them cannot settle
|
|
63
|
+
#: the argument it exists to settle.
|
|
64
|
+
BURN_IN_FIELDS = ("source_timecode", "record_timecode", "clip_name", "frame_count", "version")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def burn_in_spec(*, version: str, extra_fields: Optional[Sequence[str]] = None) -> Dict[str, Any]:
|
|
68
|
+
"""The timecode-burn picture reference spec.
|
|
69
|
+
|
|
70
|
+
Rendering it needs a live Resolve; this is the specification the render must
|
|
71
|
+
satisfy, which is the half that gets forgotten.
|
|
72
|
+
"""
|
|
73
|
+
fields = list(BURN_IN_FIELDS) + [f for f in (extra_fields or []) if f not in BURN_IN_FIELDS]
|
|
74
|
+
return {
|
|
75
|
+
"kind": "burn_in_spec",
|
|
76
|
+
"fields": fields,
|
|
77
|
+
"version": version,
|
|
78
|
+
"codec": "H.264 or ProRes 422 Proxy — this is a reference, not a master",
|
|
79
|
+
"requirement": (
|
|
80
|
+
"Both source AND record timecode. One identifies the material, the other "
|
|
81
|
+
"the position in the cut; a reference carrying only one cannot settle the "
|
|
82
|
+
"argument it exists to settle."
|
|
83
|
+
),
|
|
84
|
+
"note": (
|
|
85
|
+
"Rendering needs a live Resolve — this is the spec that render must meet. "
|
|
86
|
+
"The loudest instruction in online editing is to never send a turnover "
|
|
87
|
+
"without one of these: without it the receiving editor cannot verify "
|
|
88
|
+
"anything against your intent."
|
|
89
|
+
),
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def validate_package(
|
|
94
|
+
destination: str,
|
|
95
|
+
contents: Mapping[str, Any],
|
|
96
|
+
*,
|
|
97
|
+
handle_frames: Optional[int] = None,
|
|
98
|
+
) -> Dict[str, Any]:
|
|
99
|
+
"""Check a proposed turnover against its specification.
|
|
100
|
+
|
|
101
|
+
`contents` maps a spec item to whatever represents it — a path, True, a
|
|
102
|
+
count. A falsy or absent value counts as missing.
|
|
103
|
+
"""
|
|
104
|
+
key = str(destination or "").strip().lower()
|
|
105
|
+
if key not in PACKAGE_SPECS:
|
|
106
|
+
return {
|
|
107
|
+
"success": False,
|
|
108
|
+
"error": f"unknown destination {destination!r}; expected one of "
|
|
109
|
+
f"{', '.join(sorted(PACKAGE_SPECS))}",
|
|
110
|
+
}
|
|
111
|
+
spec = PACKAGE_SPECS[key]
|
|
112
|
+
required_handles = HANDLE_REQUIREMENTS[key]
|
|
113
|
+
|
|
114
|
+
missing = [item for item in spec["required"] if not contents.get(item)]
|
|
115
|
+
absent_recommended = [item for item in spec["recommended"] if not contents.get(item)]
|
|
116
|
+
|
|
117
|
+
handle_finding = None
|
|
118
|
+
if handle_frames is not None and handle_frames < required_handles["frames"]:
|
|
119
|
+
handle_finding = (
|
|
120
|
+
f"handles are {handle_frames} frames; {key} needs at least "
|
|
121
|
+
f"{required_handles['frames']} — {required_handles['note']}"
|
|
122
|
+
)
|
|
123
|
+
if "handles" not in missing:
|
|
124
|
+
missing.append("handles")
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
"success": True,
|
|
128
|
+
"kind": "turnover_manifest",
|
|
129
|
+
"destination": key,
|
|
130
|
+
"complete": not missing,
|
|
131
|
+
"missing_required": missing,
|
|
132
|
+
"missing_recommended": absent_recommended,
|
|
133
|
+
"handle_requirement": required_handles,
|
|
134
|
+
"handle_finding": handle_finding,
|
|
135
|
+
"audio_spec": spec.get("audio"),
|
|
136
|
+
"burn_in_required": True,
|
|
137
|
+
"note": (
|
|
138
|
+
f"{key} turnover is COMPLETE against spec."
|
|
139
|
+
if not missing else
|
|
140
|
+
f"{key} turnover is INCOMPLETE — missing: {', '.join(missing)}. "
|
|
141
|
+
"These are blockers: a package that ships without them fails at the "
|
|
142
|
+
"receiving facility, after they have started, and the fix costs a day "
|
|
143
|
+
"of someone else's schedule."
|
|
144
|
+
) + (
|
|
145
|
+
f" Also absent (recommended, not blocking): {', '.join(absent_recommended)}."
|
|
146
|
+
if absent_recommended else ""
|
|
147
|
+
),
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def plan_turnover(
|
|
152
|
+
destinations: Sequence[str],
|
|
153
|
+
contents: Mapping[str, Any],
|
|
154
|
+
*,
|
|
155
|
+
version: str = "v01",
|
|
156
|
+
handle_frames: Optional[int] = None,
|
|
157
|
+
) -> Dict[str, Any]:
|
|
158
|
+
"""Validate one set of contents against several destinations at once."""
|
|
159
|
+
if not destinations:
|
|
160
|
+
return {"success": False, "error": "No destinations supplied"}
|
|
161
|
+
packages = []
|
|
162
|
+
for destination in destinations:
|
|
163
|
+
result = validate_package(destination, contents, handle_frames=handle_frames)
|
|
164
|
+
if not result.get("success"):
|
|
165
|
+
return result
|
|
166
|
+
packages.append(result)
|
|
167
|
+
|
|
168
|
+
incomplete = [p["destination"] for p in packages if not p["complete"]]
|
|
169
|
+
return {
|
|
170
|
+
"success": True,
|
|
171
|
+
"kind": "turnover_plan",
|
|
172
|
+
"version": version,
|
|
173
|
+
"packages": packages,
|
|
174
|
+
"burn_in_spec": burn_in_spec(version=version),
|
|
175
|
+
"incomplete_destinations": incomplete,
|
|
176
|
+
"all_complete": not incomplete,
|
|
177
|
+
"note": (
|
|
178
|
+
"All packages complete against spec."
|
|
179
|
+
if not incomplete else
|
|
180
|
+
f"{len(incomplete)} of {len(packages)} packages incomplete: "
|
|
181
|
+
f"{', '.join(incomplete)}."
|
|
182
|
+
) + (
|
|
183
|
+
" Manifests only — nothing is exported. An export that runs perfectly "
|
|
184
|
+
"and omits the textless is still a failed turnover, so the checking is "
|
|
185
|
+
"the useful half; rendering needs a live Resolve."
|
|
186
|
+
),
|
|
187
|
+
}
|