davinci-resolve-mcp 2.133.0 → 2.134.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/docs/guides/native-drt-authoring.md +11 -0
- package/install.py +1 -1
- package/package.json +1 -1
- package/resolve-advanced/server/author-interchange.mjs +9 -4
- package/resolve-advanced/server/tools/drt.mjs +2 -2
- package/resolve-advanced/vendor/drp-format/assemble-timeline.js +53 -22
- package/resolve-advanced/vendor/drp-format/cut-media.js +4 -2
- package/resolve-advanced/vendor/drp-format/media-timemap.js +46 -0
- package/resolve-advanced/vendor/drp-format/splice-clips.js +9 -1
- package/resolve-advanced/vendor/drp-format/templates/compound-pool-r19.xml +1 -1
- package/src/granular/common.py +1 -1
- package/src/server.py +1 -1
|
@@ -45,6 +45,7 @@ window. `render.verify_output` covers the container-level checks.
|
|
|
45
45
|
| Audio cross-fades | `transitions[].trackType: 'audio'` | v2.116 |
|
|
46
46
|
| Constant retimes, forward | `cuts[].speed` (e.g. `0.5`) | v2.113 |
|
|
47
47
|
| Constant retimes, reverse | `cuts[].reverse` | v2.114 |
|
|
48
|
+
| Freeze frames | `cuts[].freeze` (holds source frame `srcIn`) | v2.134 |
|
|
48
49
|
| Audio placements, A1–A8 | `cuts[].audioOnly + track` | v2.115 |
|
|
49
50
|
| Built-in generators | `elements: [{type:'generator', generatorName}]` | v2.110 |
|
|
50
51
|
| Custom start timecode | `spec.startFrame` / `preserveStartTimecode` | v2.117 |
|
|
@@ -54,6 +55,7 @@ window. `render.verify_output` covers the container-level checks.
|
|
|
54
55
|
| Subtitles | `spec.subtitles` / `spec.subtitlesSrt` (raw SRT) | v2.128 |
|
|
55
56
|
| Compound clips | survive `extract_from_drp` → `.drt` (inner containers kept, recursive) | v2.130 |
|
|
56
57
|
| Compound authoring | `spec.compounds` (multiple compose; nested edits render) | v2.131–2.132 |
|
|
58
|
+
| Nested compounds | `compounds[].compounds` (depth-2 playback render-verified) | v2.134 |
|
|
57
59
|
| Fusion titles | `elements: [{type:'title', text}]` — **21-gen hosts only** | v2.108 |
|
|
58
60
|
|
|
59
61
|
`assemble_from_interchange` drives the same engine from an EDL / OTIO /
|
|
@@ -93,6 +95,15 @@ the clip's `<In>`/`<Duration>` window into it in record-domain frames.
|
|
|
93
95
|
Reverse is the same map with the Y endpoints swapped, and `In` then measures
|
|
94
96
|
from the source end.
|
|
95
97
|
|
|
98
|
+
**Freezes are a third shape, not a flat retime.** A flat line in the retime's
|
|
99
|
+
frame domain reads back frozen but *renders moving* — the one divergence that
|
|
100
|
+
runs in the "working" direction. The real freeze (harvested from a live EDL
|
|
101
|
+
`M2 000.0` import, render-proven frozen by freezedetect) is a flat line in
|
|
102
|
+
**seconds**: `YMin = YMax = Y = frozen position` (source frame / fps),
|
|
103
|
+
`XMax = 60000` (a fixed sentinel, not the clip length), and the clip's `<In>`
|
|
104
|
+
left **empty**. `cuts[].freeze: true` authors exactly that; EDL `M2 000.0`
|
|
105
|
+
and zero-speed warps route to it through `assemble_from_interchange`.
|
|
106
|
+
|
|
96
107
|
**Timeline origin.** Template timelines start at frame 86400
|
|
97
108
|
(01:00:00:00 @ 24fps). Clips placed before the origin are silently dropped
|
|
98
109
|
by Resolve on import — `startFrame` is timeline-absolute.
|
package/install.py
CHANGED
|
@@ -37,7 +37,7 @@ from src.utils.update_check import (
|
|
|
37
37
|
|
|
38
38
|
# ─── Version ──────────────────────────────────────────────────────────────────
|
|
39
39
|
|
|
40
|
-
VERSION = "2.
|
|
40
|
+
VERSION = "2.134.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
|
@@ -201,9 +201,10 @@ export function eventsToOTIO(events, opts = {}) {
|
|
|
201
201
|
* fps (v2.104.6 convention, measured against Resolve); the assemble template
|
|
202
202
|
* timeline runs 24fps with origin 86400, so rec/src frames convert as
|
|
203
203
|
* round(frames × 24 / nominalFps). Placement anchors the EARLIEST video
|
|
204
|
-
* event at the origin. Honesty ledger in the returned report:
|
|
205
|
-
*
|
|
206
|
-
* as real Sm2TimeMaps, r19 keyed form, readback/render-verified
|
|
204
|
+
* event at the origin. Honesty ledger in the returned report: authored
|
|
205
|
+
* retimes (constant speed forward AND reverse, and zero-speed FREEZES — all
|
|
206
|
+
* AUTHORED as real Sm2TimeMaps, r19 keyed form, readback/render-verified;
|
|
207
|
+
* freezes render-proven frozen via freezedetect, E55/E56), authored vs
|
|
207
208
|
* dropped transitions (cross-dissolves are AUTHORED when the predecessor
|
|
208
209
|
* abuts the cut and both sides have handle media — render-verified on
|
|
209
210
|
* 19.1.3.7; otherwise dropped with the reason, as a cut at the boundary),
|
|
@@ -286,7 +287,11 @@ export function eventsToAssembleSpec(events, opts = {}) {
|
|
|
286
287
|
if ((e.speed ?? 100) !== 100 || e.reverse) {
|
|
287
288
|
const spd = Math.abs(e.speed ?? 100);
|
|
288
289
|
if (!(spd > 0)) {
|
|
289
|
-
|
|
290
|
+
// FREEZE (EDL M2 000.0 / zero-speed OTIO warp): authored as the real
|
|
291
|
+
// freeze Sm2TimeMap harvested live in E55 — holds srcIn for the
|
|
292
|
+
// whole cut (render-proven frozen via freezedetect on 19.1.3.7).
|
|
293
|
+
cut.freeze = true;
|
|
294
|
+
authoredRetimes.push({ index: e.index, source: e.source, speed: 0, freeze: true });
|
|
290
295
|
} else {
|
|
291
296
|
// Constant speed, forward or reverse: authored as a real Sm2TimeMap
|
|
292
297
|
// on the cut (r19 keyed form; readback/render-verified on 19.1.3.7 —
|
|
@@ -51,7 +51,7 @@ const assembleSchema = z.object({
|
|
|
51
51
|
spec: z
|
|
52
52
|
.object({})
|
|
53
53
|
.passthrough()
|
|
54
|
-
.describe("assembleTimeline spec: { timelineName?, startFrame? (timeline start frame @24, default 86400=01:00:00:00 — sets the start TIMECODE, render-verified on 19), media?: {mediaFilePath, spec:{width,height,frameCount,fps}, cuts:[{startFrame,durationFrames,srcIn?,track? (1-based video track; >1 = video-only, render-verified stacking),speed?/reverse? (constant retime, e.g. 0.5, forward or backwards; video-only; readback+render-verified on 19),audioOnly?+track? (explicit AUDIO placement on audio track 1-8; presence suppresses the A1 mirror; render-verified on 19)}]} | [same, ...] (multi-source needs media_pool.capture_media_template run once per file), transitions?: [{track, atFrame, durationFrames?, trackType? ('video' dissolve | 'audio' cross-fade, both render-verified on 19)}], markers?: [{frame (timeline-absolute), color? (16 names), name?, note?, duration?, customData?}] (readback-verified on 19), compounds?: [{name, startFrame (parent, absolute), durationFrames, track?, cuts:[{mediaFilePath, startFrame (INNER, 0-based), durationFrames, srcIn?}]}] (multiple PARALLEL compounds compose — render-verified
|
|
54
|
+
.describe("assembleTimeline spec: { timelineName?, startFrame? (timeline start frame @24, default 86400=01:00:00:00 — sets the start TIMECODE, render-verified on 19), media?: {mediaFilePath, spec:{width,height,frameCount,fps}, cuts:[{startFrame,durationFrames,srcIn?,track? (1-based video track; >1 = video-only, render-verified stacking),speed?/reverse? (constant retime, e.g. 0.5, forward or backwards; video-only; readback+render-verified on 19),freeze? (true = hold source frame srcIn for the whole cut; video-only; render-proven frozen on 19 via freezedetect),audioOnly?+track? (explicit AUDIO placement on audio track 1-8; presence suppresses the A1 mirror; render-verified on 19)}]} | [same, ...] (multi-source needs media_pool.capture_media_template run once per file), transitions?: [{track, atFrame, durationFrames?, trackType? ('video' dissolve | 'audio' cross-fade, both render-verified on 19)}], markers?: [{frame (timeline-absolute), color? (16 names), name?, note?, duration?, customData?}] (readback-verified on 19), compounds?: [{name, startFrame (parent, absolute), durationFrames, track?, cuts:[{mediaFilePath, startFrame (INNER, 0-based), durationFrames, srcIn?}], compounds?: [same, nested — frames inner-relative]}] (multiple PARALLEL compounds compose AND compounds NEST — depth-2 playback render-verified on 19 (the old depth-2 black was a missing SequenceSetup key, fixed); deeper nesting composes but is unverified; inner cuts need captured templates w/ native clips), subtitles?: [{startFrame (timeline-absolute), durationFrames, text}] + subtitlesSrt? (raw SRT, cues anchor at the origin; readback-verified on 19; angle-bracket runs read as SRT markup), elements?: [{type:'title'|'generator', track, startFrame, durationFrames?, text?, generatorName? ('Solid Color'|'SMPTE Color Bar'|'Grey Scale' render-verified on 19), ...}] }. startFrame is timeline-absolute (origin 86400)."),
|
|
55
55
|
outputPath: z.string().describe('Absolute path where the importable .drt will be written'),
|
|
56
56
|
targetAppVersion: z
|
|
57
57
|
.union([z.string(), z.number()])
|
|
@@ -153,7 +153,7 @@ function requirePathArg(args, key, action) {
|
|
|
153
153
|
export const drtTool = {
|
|
154
154
|
name: 'drt',
|
|
155
155
|
description:
|
|
156
|
-
'DaVinci Resolve Timeline (.drt) operations — offline, no Resolve required. Actions: assemble_from_interchange (EDL/OTIO/XML/AAF + sourceMap → IMPORTABLE RENDERING native .drt in one call; retimes
|
|
156
|
+
'DaVinci Resolve Timeline (.drt) operations — offline, no Resolve required. Actions: assemble_from_interchange (EDL/OTIO/XML/AAF + sourceMap → IMPORTABLE RENDERING native .drt in one call; retimes AUTHOR — constant speed fwd/rev AND zero-speed freezes (EDL M2 000.0; render-proven frozen); cross-dissolves are AUTHORED when the cut abuts with handles both sides (render-verified on 19), else dropped with reason; ledger in `conform`), assemble (spec → IMPORTABLE native-schema .drt via template-spliced real structures; pass targetAppVersion e.g. \'19.1\' for pre-21 hosts), parse, list_sequences (enumerate the timelines inside a .drp/.drt → [{id,name,eventCount,index}] to drive a "which sequence?" picker), author, validate, inject_into_drp, extract_from_drp (pull one SeqContainer out as a .drt — feed the .drt to the Python davinci-resolve MCP timeline.import_timeline_checked, or use timeline.import_from_drp to do both), downgrade (stamp <ProjectVersion> down so an OLDER Resolve will import a .drt/.drp from a newer one — pass targetAppVersion like "19.1.3" or targetProjectVersion).',
|
|
157
157
|
async handler({ action, args }) {
|
|
158
158
|
if (action === 'parse') {
|
|
159
159
|
const p = parseSchema.parse(requirePathArg(args, 'drtPath', 'parse'));
|
|
@@ -25,7 +25,7 @@ const { createEmptyProject, addMediaClip, DEFAULT_START_FRAME } = require('./aut
|
|
|
25
25
|
const { loadMediaTemplate, transplantMediaElement, insertMediaElement } = require('./media-template-cache');
|
|
26
26
|
const JSZip = require('jszip');
|
|
27
27
|
const { cutSourceIntoClips } = require('./cut-media');
|
|
28
|
-
const { buildConstantSpeedTimemapKeyed } = require('./media-timemap');
|
|
28
|
+
const { buildConstantSpeedTimemapKeyed, buildFreezeTimemapKeyed } = require('./media-timemap');
|
|
29
29
|
const { encodeTimelineMarkersBlob } = require('./timeline-markers-blob');
|
|
30
30
|
const { placeSubtitles, parseSrt } = require('./place-subtitles');
|
|
31
31
|
const { placeCompound } = require('./place-compound');
|
|
@@ -137,7 +137,21 @@ async function assembleTimeline(spec = {}) {
|
|
|
137
137
|
// captured before clip elements were harvested.
|
|
138
138
|
if (caches[i] && caches[i].videoClipElement) out.donorClipVideo = caches[i].videoClipElement;
|
|
139
139
|
if (caches[i] && caches[i].audioClipElement) out.donorClipAudio = caches[i].audioClipElement;
|
|
140
|
-
if (cut.
|
|
140
|
+
if (cut.freeze || cut.speed === 0) {
|
|
141
|
+
// FREEZE frame (r19 keyed map harvested from a live EDL M2 000.0
|
|
142
|
+
// import, E55; render-proven frozen). Holds source frame srcIn for
|
|
143
|
+
// the whole cut. The frozen clip's <In> stays EMPTY; the position
|
|
144
|
+
// rides in the map (seconds domain, XMax 60000 sentinel).
|
|
145
|
+
if (cut.reverse) throw new RangeError('assembleTimeline: cut.freeze cannot combine with reverse');
|
|
146
|
+
const fpsF = Math.round(src.spec.fps || 24);
|
|
147
|
+
out.timemap = buildFreezeTimemapKeyed({
|
|
148
|
+
freezeFrame: cut.srcIn ?? 0, sourceFrames: src.spec.frameCount, fps: fpsF,
|
|
149
|
+
uniqueId: randomUUID(),
|
|
150
|
+
}).toString('hex');
|
|
151
|
+
out.emptyIn = true;
|
|
152
|
+
delete out.freeze;
|
|
153
|
+
delete out.speed;
|
|
154
|
+
} else if (cut.reverse || (cut.speed !== undefined && cut.speed !== 1)) {
|
|
141
155
|
// Constant-speed retime (forward only). The Sm2TimeMap spans the
|
|
142
156
|
// whole source stretched by 1/speed; the clip windows into it with
|
|
143
157
|
// RECORD-domain In/Duration (measured live on 19.1.3.7), so the
|
|
@@ -219,40 +233,44 @@ async function assembleTimeline(spec = {}) {
|
|
|
219
233
|
// Pool elements for compound-only sources (files not in spec.media).
|
|
220
234
|
const specFiles = new Set((Array.isArray(media) ? media : media ? [media] : []).map((src) => src.mediaFilePath));
|
|
221
235
|
const insertedExtra = new Set();
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}
|
|
236
|
+
const walkCompoundCuts = (comps) => (comps || []).flatMap((c) => [...(c.cuts || []), ...walkCompoundCuts(c.compounds)]);
|
|
237
|
+
for (const cut of walkCompoundCuts(spec.compounds)) {
|
|
238
|
+
const fp = cut.mediaFilePath;
|
|
239
|
+
if (!fp || specFiles.has(fp) || insertedExtra.has(fp)) continue;
|
|
240
|
+
const cache = loadMediaTemplate(fp);
|
|
241
|
+
if (!cache) continue; // the per-cut refusal below names it properly
|
|
242
|
+
const zipC = await JSZip.loadAsync(buffer);
|
|
243
|
+
const mpP = 'MediaPool/Master/MpFolder.xml';
|
|
244
|
+
zipC.file(mpP, insertMediaElement(await zipC.file(mpP).async('string'), cache.poolElement));
|
|
245
|
+
buffer = await zipC.generateAsync({ type: 'nodebuffer', compression: 'DEFLATE' });
|
|
246
|
+
insertedExtra.add(fp);
|
|
234
247
|
}
|
|
235
|
-
|
|
236
|
-
|
|
248
|
+
// Recursive: compounds nest (comp.compounds, frames inner-relative like
|
|
249
|
+
// cuts). Depth-2 playback is RENDER-VERIFIED on 19.1.3.7 (E56, white 234
|
|
250
|
+
// through two levels) — the historical depth-2 black was one missing key,
|
|
251
|
+
// SequenceSetup, in the donor template's embedded-sequence blob. Depths
|
|
252
|
+
// beyond 2 compose structurally but are not render-verified.
|
|
253
|
+
const placeTree = async (comp, parentUuid, label) => {
|
|
254
|
+
if (!comp || typeof comp !== 'object') throw new TypeError(`assembleTimeline: ${label} must be an object`);
|
|
237
255
|
const res = await placeCompound(buffer, {
|
|
238
|
-
name: comp.name
|
|
256
|
+
name: comp.name,
|
|
239
257
|
startFrame: comp.startFrame,
|
|
240
258
|
durationFrames: comp.durationFrames,
|
|
241
259
|
track: comp.track,
|
|
242
|
-
timelineUuid:
|
|
260
|
+
timelineUuid: parentUuid,
|
|
243
261
|
});
|
|
244
262
|
buffer = res.buffer;
|
|
245
263
|
const innerCuts = (comp.cuts || []).map((cut, i) => {
|
|
246
|
-
if (!cut.mediaFilePath) throw new TypeError(`assembleTimeline:
|
|
264
|
+
if (!cut.mediaFilePath) throw new TypeError(`assembleTimeline: ${label}.cuts[${i}].mediaFilePath is required`);
|
|
247
265
|
if (!Number.isInteger(cut.startFrame) || cut.startFrame < 0 || (cut.startFrame + (cut.durationFrames || 0)) > comp.durationFrames) {
|
|
248
|
-
throw new RangeError(`assembleTimeline:
|
|
266
|
+
throw new RangeError(`assembleTimeline: ${label}.cuts[${i}] must sit inside [0, ${comp.durationFrames}) — inner frames are 0-based`);
|
|
249
267
|
}
|
|
250
268
|
const cache = loadMediaTemplate(cut.mediaFilePath);
|
|
251
269
|
if (!cache) {
|
|
252
|
-
throw new Error(`assembleTimeline:
|
|
270
|
+
throw new Error(`assembleTimeline: ${label}.cuts[${i}] — no captured native media template for ${cut.mediaFilePath}. Run media_pool.capture_media_template(path) with Resolve open.`);
|
|
253
271
|
}
|
|
254
272
|
if (!cache.videoClipElement) {
|
|
255
|
-
throw new Error(`assembleTimeline:
|
|
273
|
+
throw new Error(`assembleTimeline: ${label}.cuts[${i}] — the media template for ${cut.mediaFilePath} predates native clip capture. Re-run media_pool.capture_media_template(path) with Resolve open.`);
|
|
256
274
|
}
|
|
257
275
|
const out = { ...cut, mediaRef: cache.mediaRef };
|
|
258
276
|
delete out.mediaFilePath;
|
|
@@ -267,6 +285,19 @@ async function assembleTimeline(spec = {}) {
|
|
|
267
285
|
const cutRes = await cutSourceIntoClips(buffer, { cuts: innerCuts, timelineUuid: res.innerContainerId });
|
|
268
286
|
buffer = cutRes.buffer;
|
|
269
287
|
}
|
|
288
|
+
for (const [cj, child] of (comp.compounds || []).entries()) {
|
|
289
|
+
if (child && typeof child === 'object' && !Number.isInteger(child.startFrame)) {
|
|
290
|
+
throw new TypeError(`assembleTimeline: ${label}.compounds[${cj}].startFrame must be an integer (INNER frames, 0-based)`);
|
|
291
|
+
}
|
|
292
|
+
await placeTree({ name: child.name || `Compound ${cj + 1}`, ...child }, res.innerContainerId, `${label}.compounds[${cj}]`);
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
for (const [ci, comp] of spec.compounds.entries()) {
|
|
296
|
+
await placeTree(
|
|
297
|
+
{ ...(comp && typeof comp === 'object' ? comp : {}), name: (comp && comp.name) || `Compound ${ci + 1}` },
|
|
298
|
+
parentContainerId,
|
|
299
|
+
`compounds[${ci}]`,
|
|
300
|
+
);
|
|
270
301
|
}
|
|
271
302
|
}
|
|
272
303
|
|
|
@@ -26,7 +26,7 @@ const {
|
|
|
26
26
|
replaceTrackVec,
|
|
27
27
|
emptyTrackClone,
|
|
28
28
|
} = require('./seq-surgery');
|
|
29
|
-
const { clipDbId, setClipStart, setClipDuration, setClipIn } = require('./splice-clips');
|
|
29
|
+
const { clipDbId, setClipStart, setClipDuration, setClipIn, setClipInEmpty } = require('./splice-clips');
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* @param {Buffer|string} drpInput
|
|
@@ -90,7 +90,9 @@ async function cutSourceIntoClips(drpInput, opts = {}) {
|
|
|
90
90
|
let c = freshDbIds(native ? native.trim() : donor);
|
|
91
91
|
c = setClipStart(c, cut.startFrame);
|
|
92
92
|
c = setClipDuration(c, cut.durationFrames);
|
|
93
|
-
|
|
93
|
+
// A frozen clip's <In> is EMPTY (measured E55 harvest); the frozen
|
|
94
|
+
// position rides in the timemap, and a windowed In breaks the render.
|
|
95
|
+
c = cut.emptyIn ? setClipInEmpty(c) : setClipIn(c, cut.srcIn ?? 0);
|
|
94
96
|
if (cut.mediaRef) {
|
|
95
97
|
// Multi-source: point this cut at ITS source's transplanted pool
|
|
96
98
|
// element instead of the donor's.
|
|
@@ -259,9 +259,55 @@ function buildConstantSpeedTimemapKeyed({ speed, sourceFrames, fps = 24, uniqueI
|
|
|
259
259
|
] });
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
+
/**
|
|
263
|
+
* FREEZE frame map — r19 keyed form, harvested from a live 19.1.3.7 EDL
|
|
264
|
+
* `M2 <reel> 000.0` import (E55, 2026-08-31; render-proven frozen by
|
|
265
|
+
* freezedetect). A real freeze is NOT the flat frame-domain line the earlier
|
|
266
|
+
* synthetic attempt used (that one reads back frozen but RENDERS moving —
|
|
267
|
+
* the readback-blind divergence measured in E41-era work). The engine's
|
|
268
|
+
* shape is a flat line in SECONDS with two extra conventions:
|
|
269
|
+
*
|
|
270
|
+
* YMin = YMax = Y(kf0) = Y(kf1) = frozen source position in SECONDS
|
|
271
|
+
* XMax = 60000 (a fixed sentinel domain, not the clip length)
|
|
272
|
+
* LastValidYOffset = (sourceFrames-1)/fps (whole-source extent, as always)
|
|
273
|
+
*
|
|
274
|
+
* The clip's <In> is EMPTY on a frozen item (record windowing does not
|
|
275
|
+
* apply to a constant map). Byte-exact vs the harvest for equal inputs.
|
|
276
|
+
*/
|
|
277
|
+
function buildFreezeTimemapKeyed({ freezeFrame, sourceFrames, fps = 24, uniqueId }) {
|
|
278
|
+
if (!Number.isInteger(freezeFrame) || freezeFrame < 0) throw new TypeError('buildFreezeTimemapKeyed: freezeFrame must be a non-negative integer (source frame to hold)');
|
|
279
|
+
if (!Number.isInteger(sourceFrames) || sourceFrames < 1) throw new TypeError('buildFreezeTimemapKeyed: sourceFrames must be a positive integer');
|
|
280
|
+
if (freezeFrame >= sourceFrames) throw new RangeError(`buildFreezeTimemapKeyed: freezeFrame ${freezeFrame} outside source (${sourceFrames} frames)`);
|
|
281
|
+
const freezeSec = freezeFrame / fps;
|
|
282
|
+
const XMAX_SENTINEL = 60000;
|
|
283
|
+
const kf = (X, Y) => encodeKeyedDict({ hdr: 1, entries: [
|
|
284
|
+
{ key: 'interp', type: 0x02, subType: 0, value: 0 },
|
|
285
|
+
{ key: 'YOut', type: T_DOUBLE, subType: 0, value: 0 },
|
|
286
|
+
{ key: 'YIn', type: T_DOUBLE, subType: 0, value: 0 },
|
|
287
|
+
{ key: 'Y', type: T_DOUBLE, subType: 0, value: Y },
|
|
288
|
+
{ key: 'XOut', type: T_DOUBLE, subType: 0, value: 0 },
|
|
289
|
+
{ key: 'XIn', type: T_DOUBLE, subType: 0, value: 0 },
|
|
290
|
+
{ key: 'X', type: T_DOUBLE, subType: 0, value: X },
|
|
291
|
+
] }).toString('hex');
|
|
292
|
+
const keyframes = encodeKeyedDict({ hdr: 1, entries: [
|
|
293
|
+
{ key: '1', type: T_BYTES, subType: 0, value: kf(XMAX_SENTINEL, freezeSec) },
|
|
294
|
+
{ key: '0', type: T_BYTES, subType: 0, value: kf(0, freezeSec) },
|
|
295
|
+
] }).toString('hex');
|
|
296
|
+
return encodeKeyedDict({ hdr: 1, entries: [
|
|
297
|
+
{ key: 'YMin', type: T_DOUBLE, subType: 0, value: freezeSec },
|
|
298
|
+
{ key: 'YMax', type: T_DOUBLE, subType: 0, value: freezeSec },
|
|
299
|
+
{ key: 'XMax', type: T_DOUBLE, subType: 0, value: XMAX_SENTINEL },
|
|
300
|
+
{ key: 'UniqueId', type: T_STRING, subType: 0, value: uniqueId },
|
|
301
|
+
{ key: 'LastValidYOffset', type: T_DOUBLE, subType: 0, value: (sourceFrames - 1) / fps },
|
|
302
|
+
{ key: 'KeyframesBA', type: T_BYTES, subType: 0, value: keyframes },
|
|
303
|
+
{ key: 'DbType', type: T_STRING, subType: 0, value: 'Sm2TimeMap' },
|
|
304
|
+
] });
|
|
305
|
+
}
|
|
306
|
+
|
|
262
307
|
module.exports = {
|
|
263
308
|
decodeTimemap, encodeTimemap, encodeRetimedTimemap,
|
|
264
309
|
identityTimemap, buildConstantSpeedTimemap, buildConstantSpeedTimemapKeyed,
|
|
310
|
+
buildFreezeTimemapKeyed,
|
|
265
311
|
buildTimemap, decodeProtobuf,
|
|
266
312
|
TYPE_LINEAR,
|
|
267
313
|
};
|
|
@@ -61,6 +61,14 @@ function setClipIn(clipXml, framePos) {
|
|
|
61
61
|
if (/<In>[^<]*<\/In>/.test(clipXml)) return clipXml.replace(/<In>[^<]*<\/In>/, enc);
|
|
62
62
|
return clipXml; // no In element to set
|
|
63
63
|
}
|
|
64
|
+
// A FROZEN clip carries an EMPTY <In/> (harvested live from an EDL M2 000.0
|
|
65
|
+
// freeze on 19.1.3.7) — the frozen position lives in its Sm2TimeMap, and
|
|
66
|
+
// record windowing does not apply to a constant map.
|
|
67
|
+
function setClipInEmpty(clipXml) {
|
|
68
|
+
if (/<In\s*\/>/.test(clipXml)) return clipXml;
|
|
69
|
+
if (/<In>[^<]*<\/In>/.test(clipXml)) return clipXml.replace(/<In>[^<]*<\/In>/, '<In/>');
|
|
70
|
+
return clipXml;
|
|
71
|
+
}
|
|
64
72
|
|
|
65
73
|
// Rewrite each clip on a track via fn(clipXml) -> clipXml. Clips have unique DbIds so a
|
|
66
74
|
// first-occurrence replace per clip is unambiguous.
|
|
@@ -445,5 +453,5 @@ async function rippleTimeline(drpInput, opts = {}) {
|
|
|
445
453
|
module.exports = {
|
|
446
454
|
moveClip, deleteClip, trimClip, trimClipHead, splitClip, rippleTimeline,
|
|
447
455
|
// low-level clip-XML helpers, reused by cut-media.js
|
|
448
|
-
clipDbId, clipStart, clipDuration, clipIn, setClipStart, setClipDuration, setClipIn,
|
|
456
|
+
clipDbId, clipStart, clipDuration, clipIn, setClipStart, setClipDuration, setClipIn, setClipInEmpty,
|
|
449
457
|
};
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
<SlateTC>-68719476737</SlateTC>
|
|
30
30
|
<Sequence>
|
|
31
31
|
<Sm2Sequence DbId="d7cfd3f0-e8b9-4048-867c-30ed6c427d83">
|
|
32
|
-
<FieldsBlob>000000010000000500000012005400680075006d0062006e00610069006c0000000c00000015200000000100000006000000100055006e0069007100750065004900640000000a000000004800300061006500660031003000370064002d0030006600390035002d0034003800610034002d0061003500330032002d003200620037003500630032006100380030003900620035000000100049006d0067005700690064007400680000000200000000d0000000140049006d0067005100750061006c006900740079000000020000000001000000120049006d00670048006500690067006800740000000200000000750000000c0044006200540079007000650000000a000000001400420074005400680075006d006e00610069006c0000000c0042007500660066006500720000000c0000001410ffd8ffe000104a46494600010101004800480000ffdb004300080606070605080707070909080a0c140d0c0b0b0c1912130f141d1a1f1e1d1a1c1c20242e2720222c231c1c2837292c30313434341f27393d38323c2e333432ffdb0043010909090c0b0c180d0d1832211c213232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232ffc0001108007500d003012200021101031101ffc4001f0000010501010101010100000000000000000102030405060708090a0bffc400b5100002010303020403050504040000017d01020300041105122131410613516107227114328191a1082342b1c11552d1f02433627282090a161718191a25262728292a3435363738393a434445464748494a535455565758595a636465666768696a737475767778797a838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae1e2e3e4e5e6e7e8e9eaf1f2f3f4f5f6f7f8f9faffc4001f0100030101010101010101010000000000000102030405060708090a0bffc400b51100020102040403040705040400010277000102031104052131061241510761711322328108144291a1b1c109233352f0156272d10a162434e125f11718191a262728292a35363738393a434445464748494a535455565758595a636465666768696a737475767778797a82838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae2e3e4e5e6e7e8e9eaf2f3f4f5f6f7f8f9faffda000c03010002110311003f00f9fe8a28a002bb9f0fff00c80edbfe05ff00a11ae1abb9f0ff00fc80edbfe05ffa11ae1cc3f84bd7fccfaae10ff7e9ff0081fe7134ebb2f09f873784d4af63e383046ddffda23f97e7e9597e17d10ea97e25994fd9612198e061dbb2f3fafb7d457a4d74e4b972a9fed15568b65dfccf0bc4be3396113ca3032b4daf7e4b78a7f6579b5bf65a75d39af16ffcb9ff00c0ff00f65ae6aba5f16ffcb9ff00c0ff00f65ae6abf47c1ff063fd753def0f3fe49bc37fdbff00fa7241599e20ff00901dcffc07ff004215a7599e20ff00901dcffc07ff004215db43f8b1f547d3669fee35bfc12fc99c3514515f447e3615f62d7c755f62d7e1de33ff00cc0ffdc4ff00dc67a7977daf97ea1451457e1a7a670de20ff90e5cff00c07ff4115995a7e20ff90e5cff00c07ff4115995f4543f851f447e379a7fbf56ff001cbf3615cd78b3fe5cff00e07ffb2d74b5cd78b7fe5cff00e07ffb2d7760ff008d1feba1f4de1e7fc94986ff00b7ff00f4dc8b7e13f11323c7a65e3e50e16090f553d94fb7a7a74e9d3b7af18af46f0a6b6752b236d3b96ba8072ccd9322f63f8743f87ad7e8b92e62e7fecf55ebd1fe9fe47d1f89bc191a17ce7031b45bfde45746fedaf57a4bcf5eaced3c3fff0021cb6ff817fe826bb9ae1bc3ff00f21cb6ff00817fe826bb9aebcc3f8abd3fccf9de10ff00719ff8dfe510a28a2b84faa3e00a28a2800aef3c3313cfa4d9c312ee92462aa3d4972057075ec1f0a74e5bab58aedf056d836d07fbc59b07f000fe38ac2b5078870a4babfd1dcf532ecd619453c463a7f629b6bcdb945457cdb48f46d2b4f8f4bd3a1b54032a32ec3f89bb9ff3ed5768a2beae108c22a11d91f80e271357155a75eb3bca4db6fbb7ab39af16ff00cb9ffc0fff0065ae6aba5f16ff00cb9ffc0fff0065ae6abdec1ff063fd753fa57c3cff00926f0dff006fff00e9c905667883fe40773ff01ffd08569d667883fe40773ff01ffd08576d0fe2c7d51f4d9a7fb8d6ff0004bf2670d451457d11f8d857d8b5f1d57d8b5f8778cfff00303ff713ff00719e9e5df6be5fa8514515f869e99c37883fe43973ff0001ff00d04566569f883fe43973ff0001ff00d0456657d150fe147d11f8de69fefd5bfc72fcd85735e2dff973ff0081ff00ecb5d2d735e2dff973ff0081ff00ecb5dd83fe347fae87d37879ff0025261bfedfff00d37239aab5a75ec9a75fc37717de8db247f78771f88aab457bd1938494a2ecd1fd2f5e853c452951aaaf19269a7d53d1a3ddbc2f3c773aa594f136e8e452ca7d8a1aef6bc6be136a065d416c5d86612cf18279da55b200f63dff00daaf65afa9ab885888c2aaeabf1bbb9f81e0b279e4d57118196bc951d9f78b8c5c5fdcd5fcee828a28ac8f40f8028a28a002be89f873a5cda578234f49d1925b8537254b03f2bfcc8463d5369f5e7f0af9dabebbbbd3a1d2248b4cb72c60b3b786de32e724aa46aa33ef815db8049d5d7b1f2fc5b394700945ef249fdcdfe6915e8a28af64fcd8e6bc5bff002e7ff03ffd96b9aae97c5bff002e7ff03ffd96b9aaf7307fc18ff5d4fe9cf0f3fe49bc37fdbfff00a7241599e20ff901dcff00c07ff4215a7599e20ff901dcff00c07ff4215db43f8b1f547d3669fee35bfc12fc99c3514515f447e3615f62d7c755f62d7e1de33ffcc0ff00dc4ffdc67a7977daf97ea1451457e1a7a670de20ff0090e5cffc07ff004115995a7e20ff0090e5cffc07ff004115995f4543f851f447e379a7fbf56ff1cbf3615cd78b7fe5cffe07ff00b2d74b5cd78b7fe5cffe07ff00b2d7760ff8d1feba1f4de1e7fc94986ffb7fff004dc8e6a8ad0d134393c49ac5be9115efd8a4b9240b8f2bccd9805beee467a63af7a75f78ebc35e1bbfb8d0aefc109a8dce9b2b59cb7a75278bed2f1928d26c0a42ee2a5b6e4e338c9afa5a3859d68f3459fd039967b87cbeaaa5562db6afa5bcd756bb1a5e02d5d346f1ae99349bcc73cc2d1820049329d8bd7b066527be01ebd2be8faf99b46f10e89f10758b5f0ce91e1bff00847350ba7f320d5a2bf79dad9a2fdee4261724f978cee18ce7b57ae7c29d5b58d5346d6e3d6f526d42e6c3599ec96e1a258f7222a63e55e07249efd7ad7ab86a52a50e5933e073cc7d1c7e2556a316b449ded76f5d74bf4b2f91ded14515d078e7c0145145006ef82a259fc79e1d85c651f53b6561ec65515f54f883fe43973ff01ffd0457cb3e04ff009287e1affb0adaff00e8d5afa9bc41ff0021cb9ff80ffe822bbb2ffe2bf4ff0023e578bffdc61fe35f948cca28a2bd83f3839af16ffcb9ff00c0ff00f65ae6aba5f16ffcb9ff00c0ff00f65ae6abdcc1ff00063fd753fa73c3cff926f0dff6ff00fe9c905667883fe40773ff0001ff00d08569d667883fe40773ff0001ff00d08576d0fe2c7d51f4d9a7fb8d6ff04bf2670d451457d11f8d857d8b5f1d57d8b5f8778cff00f303ff00713ff719e9e5df6be5fa8514515f869e99c37883fe43973ff01ffd04566569f883fe43973ff01ffd0456657d150fe147d11f8de69fefd5bfc72fcd85735e2dff00973ff81ffecb5d2d735e2dff00973ff81ffecb5dd83fe347fae87d37879ff25261bfedff00fd3721ff000e7fe47ed2bfdf7ffd16d5e57e3bff009287e25ffb0add7fe8d6aeff00c3da55deb9aedae9b63aa4da5dcce484bc833be2c293918653c804751d6bcd3c4b67369fe2ad5ecae2ee4bc9edef668a4b9933ba665720b9c927248cf53d7ad7d9e5ff00c37ea7eafc61fefb0ff0afcd9d67c12ff92bda17fdbc7fe93c95ef3f083fe3cbc5dff6335e7f28ebe6ef877a3de6bde3bd374cb0d5a7d26ea6f3765ec19df16227638c329e4023a8eb5f447c0fb596cbc3de23b49ee5eea683c41731bdc3e774aca9182c724f248cf53d6bbcf933d3e8a28a00f8028a28a00d2f0f6a29a3f8974ad4e40e63b3bc86e18200588470c700f19e3bd7d69e20ff0090e5cffc07ff004115f1cd7d5b65ac7fc241a6586ac6485e4bab482494c272a24f2d77a8e4f46dc304e411835dd97ff15fa7f91f2bc5ff00ee30ff001afca44d451457b07e70735e2dff00973ff81ffecb5cd574be2dff00973ff81ffecb5cd57b983fe0c7faea7f4e7879ff0024de1bfedfff00d3920accf107fc80ee7fe03ffa10ad3accf107fc80ee7fe03ffa10aeda1fc58faa3e9b34ff0071adfe097e4ce1a8a28afa23f1b0afb16be3aafb16bf0ef19ffe607fee27fee33d3cbbed7cbf50a28a2bf0d3d3386f107fc872e7fe03ff00a08accad3f107fc872e7fe03ff00a08accafa2a1fc28fa23f1bcd3fdfab7f8e5f9b0ae6bc5bff2e7ff0003ff00d96ba5ae6bc5bff2e7ff0003ff00d96bbb07fc68ff005d0fa6f0f3fe4a4c37fdbfff00a6e43fe1cffc8fda57fbefff00a2dabcafc77ff250fc4bff00615baffd1ad5d6092e223bed2eee2d271f727b790a489f461c8f4fc6ba3b6f13d9456b0c775e11f0edfdc2a0596eeeec5649a7603992463cb3b1e493d4926beb30988a74a0d4bb9fb5f10e4d8bc7626352824d28db7b756721f04bfe4af685ff006f1ffa4f257bcfc21ff8f2f177fd8cf79fca3af359b5c4d55adb4dd2742d1741bebcba82da2d4f4cb4f26e2df7caaa4a3a90464120e0f2091debdb7c19e0fb6f05e9775656d7b7779f6abb7bb966bb60d233b2a839200cfddcf3ce49af4a9558d55cd13e331d80ad81a8a957566d5fbe9aff0091d1d14515a1c47c0383e9460fa1af5ab3c66b7ecfb578b5738e4fb1f8ff00c03ebeb70a7b3ff97dff0092ff00f6c78360fa57bcfc28bdf3fc1b15ab7deb677c71fc2ccc47ebbab7ecfb574167d4572478b1e1e6a4a95ffedeff00807cee71c2f4f17859d09d4d5dacedb34d6b6beba5d6eb7bf93868ae96d3eed1375aaff8885adbeadff93fff006a7c056e06f65ff3117ffb77ff00b63cd7c5bff2e7ff0003ff00d96b9aaf5e9ab3e6ef5ea61fc49e48287d57ff0027ff00ed0fb7c9388bfb072da780f67cfc97d6fcb7bc9cb6b3ef6dcf31accf107fc80ee7fe03ff00a10af5298d507fbd5df47c48f7d4beadb7f7ff00fb433cc3c49e7a13a3f55f89357e7eeadfc8781d15efcb532d7d1d0f11bdaffcc35bfedfff00ed0f837c4b6ff975ff00937fc03e7bc57d8b9ae112a64af9ee2c8ffad7ec3fe5d7b3e6fef5f9b97fc36b72f9ee6b438c7d85ff00737bff007bff00b53b5a2b8b9bfd5d61de7435f374bc36e7ff0098affc93ff00b73b6971b7b47fc0ff00c9bffb535fc41ff21cb9ff0080ff00e822b32b9bbcef58179d0d7d161fc37bc547eb3b7f73ff00b73c1a986fadd79d6bdb99b76ded777f23d0eb9af16ffcb9ff00c0ff00f65ae06f3bd67c7f78d7a987f0c7924aa7d6b6fee7ff006e7d370c617fb2f32a58fbf3725f4daf78b8efaf7bec7534562c3daafc3dabaa5c03cbff00311ff927ff006c7ecb438a3daffcbab7fdbdff0000f41f85566d3f8dedee39096e8e723a64a3003f224fe15efb5f29435a10f6ada9f0a2a10e5556ff00f6effc13cfc6e5f5335c4baf3a9cab4495af656daf757d6ef65bdba1f4ed15f33b542f5cb5f27f65f6eff2ff008242e13bff00cbeffc97ff00b633ad754d3d7ef5fda8faccbfe35b76bae690b8ddaa590fadc27f8d78a515f1d532884f79339eaf14d6a9bd35f7b3e87b4f12684b8ddad69c3eb749fe35d1586bda3c88244d5ac590f46170841fc735f2b5773e1fff00901db7fc0bff004235e4e3720a7087373b36ca6abcd7112a33f76cafa7aa5fa9f49e9dab69b73958350b594a8c90932b63eb83534d7501e93467fe042bc1b4ad4a5d2b508eea2e76f0c99c075ee0ff009eb8af52b1bd8351b38eeadd898dc77ea0f707de8cb7847098c4ef55a92e965b773e438e658ec86a4670a6a7467b4bb4bac5fe6bbebd99ab757d690e3cdba863ddd37c8067f3acc9752b039c5edb1ffb6abfe3581e2dff00973ff81ffecb5cd57d461bc39c2ce0a7ede5f723ab20e1ea79ee574b1f566e2e7cda25a2b49c7afa1dbcb7d667a5d407feda0aa13ea165129924bbb7441d59a4000fc735cbd667883fe40773ff0001ff00d08577d1f0eb0bcea3ede5abec8798787385850a95bdbcaf14decba2b9daaebba3ff00d056c7ff000213fc6a65d7f47ffa0b587fe04a7f8d785d15f4747c3bc352dab4bee47e7cf87293fb6cf795f1068a3fe62f61ff008129fe35bcb6775ff3ed37fdf06be68afb16be538d25feaafd5fd87ef3da736fa5b9797b7f88df0dc2142bdef51ab7923919acee8c7816d31ff801ac7bbd32fd81db65727e9137f857a3515f234fc46c5436a11fbd9dd4f82e8537a557f723c5efac6ed1ca3db4cae3aa94208fc2b02eb4fbd607167707e919ff000af4ef107fc872e7fe03ff00a08accafa2c3f88b8a5052f611d7cd9f2f5b10f09889d18aba8b6bee763ca6eb48d4db3b74ebb3f481bfc2b31b4cbfb761e7d95cc5bb38df132e7f315ed35cd78b7fe5cffe07ff00b2d7a987f12f1539a87b08ebe6cfa7e14c4bcd334a5819ae553e6d56fa45cbf43848ade61d6371f8569d9585e5ce7ecf693cdb71bbcb8cb633eb8ab36b6d35e5cc76f021796438503fcf4af53d274b8748b05b5889639dcee7f89bb9f6e95ef65fc578ac6c9de92515d6efee3ed78bb1984e17a3050973d69ed17a69d5bb74e8bbbf4679d5b689ab4b2048f4bbd773d156ddc93fa56ac5e1bd7475d17511ff006eaffe15ea3e1fff0090e5b7fc0bff004135dcd698bcfaa539f2a8239387b8cabe330eeb4a925676ddf64ff53e7a6f0e6b9ff405d47ff015ff00c2a26f0debbff404d4bff015ff00c2be8aa2bcaad9bceaef147d1ae2bacbfe5dafbd9f00514515e41f2815dcf87ffe4076dff02ffd08d14570e61fc25ebfe67d57087fbf4ffc0ff389a75b9e17d5a7d3f548add70f05cc8b1ba1ec49c061ee33f8fe4414579d85a92a75a3283b3b9f619f612862f2caf47111528b8bd1f92ba7ea9ea9f4674de2dff973ff0081ff00ecb5cd51457ec383fe0c7faea7c87879ff0024de1bfedfff00d3920accf107fc80ee7fe03ffa10a28aeda1fc58faa3e9b34ff71adfe097e4ce1a8a28afa23f1b0afb168a2bf0ef19ff00e607fee27fee33d3cbbed7cbf50a28a2bf0d3d3386f107fc872e7fe03ffa08acca28afa2a1fc28fa23f1bcd3fdfab7f8e5f9b0ae6bc5bff2e7ff0003ff00d968a2bbb07fc68ff5d0fa6f0f3fe4a4c37fdbff00fa6e474be17d1edf4fd362b95f9ee2e23576723a0201da3dbf9fe58dda28afd83094e34e84630565647c8e7f8caf8cccebd6c449ca5ccd5df64ec97a25b234fc3fff0021cb6ff817fe826bb9a28af3f30fe2af4ff33ebf843fdc67fe37f94428a28ae13ea8ffd90000001800530065007100750065006e0063006500540079007000650000000200000000010000000c0053006500710052006500660000000a000000004800330035006300370034006300340061002d0063003300610030002d0034006100370038002d0039003600310064002d003900350039003400640066003600380034003600300038000000300049007300520065006e006400650072004300610063006800650042004100560065007200730069006f006e006500640000000100010000002400460069007200730074004d00610069006e0042007500730046006f0072006d00610074000000020000000002</FieldsBlob>
|
|
32
|
+
<FieldsBlob>000000010000000600000012005400680075006d0062006e00610069006c0000000c00000015200000000100000006000000100055006e0069007100750065004900640000000a000000004800300061006500660031003000370064002d0030006600390035002d0034003800610034002d0061003500330032002d003200620037003500630032006100380030003900620035000000100049006d0067005700690064007400680000000200000000d0000000140049006d0067005100750061006c006900740079000000020000000001000000120049006d00670048006500690067006800740000000200000000750000000c0044006200540079007000650000000a000000001400420074005400680075006d006e00610069006c0000000c0042007500660066006500720000000c0000001410ffd8ffe000104a46494600010101004800480000ffdb004300080606070605080707070909080a0c140d0c0b0b0c1912130f141d1a1f1e1d1a1c1c20242e2720222c231c1c2837292c30313434341f27393d38323c2e333432ffdb0043010909090c0b0c180d0d1832211c213232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232ffc0001108007500d003012200021101031101ffc4001f0000010501010101010100000000000000000102030405060708090a0bffc400b5100002010303020403050504040000017d01020300041105122131410613516107227114328191a1082342b1c11552d1f02433627282090a161718191a25262728292a3435363738393a434445464748494a535455565758595a636465666768696a737475767778797a838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae1e2e3e4e5e6e7e8e9eaf1f2f3f4f5f6f7f8f9faffc4001f0100030101010101010101010000000000000102030405060708090a0bffc400b51100020102040403040705040400010277000102031104052131061241510761711322328108144291a1b1c109233352f0156272d10a162434e125f11718191a262728292a35363738393a434445464748494a535455565758595a636465666768696a737475767778797a82838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae2e3e4e5e6e7e8e9eaf2f3f4f5f6f7f8f9faffda000c03010002110311003f00f9fe8a28a002bb9f0fff00c80edbfe05ff00a11ae1abb9f0ff00fc80edbfe05ffa11ae1cc3f84bd7fccfaae10ff7e9ff0081fe7134ebb2f09f873784d4af63e383046ddffda23f97e7e9597e17d10ea97e25994fd9612198e061dbb2f3fafb7d457a4d74e4b972a9fed15568b65dfccf0bc4be3396113ca3032b4daf7e4b78a7f6579b5bf65a75d39af16ffcb9ff00c0ff00f65ae6aba5f16ffcb9ff00c0ff00f65ae6abf47c1ff063fd753def0f3fe49bc37fdbff00fa7241599e20ff00901dcffc07ff004215a7599e20ff00901dcffc07ff004215db43f8b1f547d3669fee35bfc12fc99c3514515f447e3615f62d7c755f62d7e1de33ff00cc0ffdc4ff00dc67a7977daf97ea1451457e1a7a670de20ff90e5cff00c07ff4115995a7e20ff90e5cff00c07ff4115995f4543f851f447e379a7fbf56ff001cbf3615cd78b3fe5cff00e07ffb2d74b5cd78b7fe5cff00e07ffb2d7760ff008d1feba1f4de1e7fc94986ff00b7ff00f4dc8b7e13f11323c7a65e3e50e16090f553d94fb7a7a74e9d3b7af18af46f0a6b6752b236d3b96ba8072ccd9322f63f8743f87ad7e8b92e62e7fecf55ebd1fe9fe47d1f89bc191a17ce7031b45bfde45746fedaf57a4bcf5eaced3c3fff0021cb6ff817fe826bb9ae1bc3ff00f21cb6ff00817fe826bb9aebcc3f8abd3fccf9de10ff00719ff8dfe510a28a2b84faa3e00a28a2800aef3c3313cfa4d9c312ee92462aa3d4972057075ec1f0a74e5bab58aedf056d836d07fbc59b07f000fe38ac2b5078870a4babfd1dcf532ecd619453c463a7f629b6bcdb945457cdb48f46d2b4f8f4bd3a1b54032a32ec3f89bb9ff3ed5768a2beae108c22a11d91f80e271357155a75eb3bca4db6fbb7ab39af16ff00cb9ffc0fff0065ae6aba5f16ff00cb9ffc0fff0065ae6abdec1ff063fd753fa57c3cff00926f0dff006fff00e9c905667883fe40773ff01ffd08569d667883fe40773ff01ffd08576d0fe2c7d51f4d9a7fb8d6ff0004bf2670d451457d11f8d857d8b5f1d57d8b5f8778cfff00303ff713ff00719e9e5df6be5fa8514515f869e99c37883fe43973ff0001ff00d04566569f883fe43973ff0001ff00d0456657d150fe147d11f8de69fefd5bfc72fcd85735e2dff973ff0081ff00ecb5d2d735e2dff973ff0081ff00ecb5dd83fe347fae87d37879ff0025261bfedfff00d37239aab5a75ec9a75fc37717de8db247f78771f88aab457bd1938494a2ecd1fd2f5e853c452951aaaf19269a7d53d1a3ddbc2f3c773aa594f136e8e452ca7d8a1aef6bc6be136a065d416c5d86612cf18279da55b200f63dff00daaf65afa9ab885888c2aaeabf1bbb9f81e0b279e4d57118196bc951d9f78b8c5c5fdcd5fcee828a28ac8f40f8028a28a002be89f873a5cda578234f49d1925b8537254b03f2bfcc8463d5369f5e7f0af9dabebbbbd3a1d2248b4cb72c60b3b786de32e724aa46aa33ef815db8049d5d7b1f2fc5b394700945ef249fdcdfe6915e8a28af64fcd8e6bc5bff002e7ff03ffd96b9aae97c5bff002e7ff03ffd96b9aaf7307fc18ff5d4fe9cf0f3fe49bc37fdbfff00a7241599e20ff901dcff00c07ff4215a7599e20ff901dcff00c07ff4215db43f8b1f547d3669fee35bfc12fc99c3514515f447e3615f62d7c755f62d7e1de33ffcc0ff00dc4ffdc67a7977daf97ea1451457e1a7a670de20ff0090e5cffc07ff004115995a7e20ff0090e5cffc07ff004115995f4543f851f447e379a7fbf56ff1cbf3615cd78b7fe5cffe07ff00b2d74b5cd78b7fe5cffe07ff00b2d7760ff8d1feba1f4de1e7fc94986ffb7fff004dc8e6a8ad0d134393c49ac5be9115efd8a4b9240b8f2bccd9805beee467a63af7a75f78ebc35e1bbfb8d0aefc109a8dce9b2b59cb7a75278bed2f1928d26c0a42ee2a5b6e4e338c9afa5a3859d68f3459fd039967b87cbeaaa5562db6afa5bcd756bb1a5e02d5d346f1ae99349bcc73cc2d1820049329d8bd7b066527be01ebd2be8faf99b46f10e89f10758b5f0ce91e1bff00847350ba7f320d5a2bf79dad9a2fdee4261724f978cee18ce7b57ae7c29d5b58d5346d6e3d6f526d42e6c3599ec96e1a258f7222a63e55e07249efd7ad7ab86a52a50e5933e073cc7d1c7e2556a316b449ded76f5d74bf4b2f91ded14515d078e7c0145145006ef82a259fc79e1d85c651f53b6561ec65515f54f883fe43973ff01ffd0457cb3e04ff009287e1affb0adaff00e8d5afa9bc41ff0021cb9ff80ffe822bbb2ffe2bf4ff0023e578bffdc61fe35f948cca28a2bd83f3839af16ffcb9ff00c0ff00f65ae6aba5f16ffcb9ff00c0ff00f65ae6abdcc1ff00063fd753fa73c3cff926f0dff6ff00fe9c905667883fe40773ff0001ff00d08569d667883fe40773ff0001ff00d08576d0fe2c7d51f4d9a7fb8d6ff04bf2670d451457d11f8d857d8b5f1d57d8b5f8778cff00f303ff00713ff719e9e5df6be5fa8514515f869e99c37883fe43973ff01ffd04566569f883fe43973ff01ffd0456657d150fe147d11f8de69fefd5bfc72fcd85735e2dff00973ff81ffecb5d2d735e2dff00973ff81ffecb5dd83fe347fae87d37879ff25261bfedff00fd3721ff000e7fe47ed2bfdf7ffd16d5e57e3bff009287e25ffb0add7fe8d6aeff00c3da55deb9aedae9b63aa4da5dcce484bc833be2c293918653c804751d6bcd3c4b67369fe2ad5ecae2ee4bc9edef668a4b9933ba665720b9c927248cf53d7ad7d9e5ff00c37ea7eafc61fefb0ff0afcd9d67c12ff92bda17fdbc7fe93c95ef3f083fe3cbc5dff6335e7f28ebe6ef877a3de6bde3bd374cb0d5a7d26ea6f3765ec19df16227638c329e4023a8eb5f447c0fb596cbc3de23b49ee5eea683c41731bdc3e774aca9182c724f248cf53d6bbcf933d3e8a28a00f8028a28a00d2f0f6a29a3f8974ad4e40e63b3bc86e18200588470c700f19e3bd7d69e20ff0090e5cffc07ff004115f1cd7d5b65ac7fc241a6586ac6485e4bab482494c272a24f2d77a8e4f46dc304e411835dd97ff15fa7f91f2bc5ff00ee30ff001afca44d451457b07e70735e2dff00973ff81ffecb5cd574be2dff00973ff81ffecb5cd57b983fe0c7faea7f4e7879ff0024de1bfedfff00d3920accf107fc80ee7fe03ffa10ad3accf107fc80ee7fe03ffa10aeda1fc58faa3e9b34ff0071adfe097e4ce1a8a28afa23f1b0afb16be3aafb16bf0ef19ffe607fee27fee33d3cbbed7cbf50a28a2bf0d3d3386f107fc872e7fe03ff00a08accad3f107fc872e7fe03ff00a08accafa2a1fc28fa23f1bcd3fdfab7f8e5f9b0ae6bc5bff2e7ff0003ff00d96ba5ae6bc5bff2e7ff0003ff00d96bbb07fc68ff005d0fa6f0f3fe4a4c37fdbfff00a6e43fe1cffc8fda57fbefff00a2dabcafc77ff250fc4bff00615baffd1ad5d6092e223bed2eee2d271f727b790a489f461c8f4fc6ba3b6f13d9456b0c775e11f0edfdc2a0596eeeec5649a7603992463cb3b1e493d4926beb30988a74a0d4bb9fb5f10e4d8bc7626352824d28db7b756721f04bfe4af685ff006f1ffa4f257bcfc21ff8f2f177fd8cf79fca3af359b5c4d55adb4dd2742d1741bebcba82da2d4f4cb4f26e2df7caaa4a3a90464120e0f2091debdb7c19e0fb6f05e9775656d7b7779f6abb7bb966bb60d233b2a839200cfddcf3ce49af4a9558d55cd13e331d80ad81a8a957566d5fbe9aff0091d1d14515a1c47c0383e9460fa1af5ab3c66b7ecfb578b5738e4fb1f8ff00c03ebeb70a7b3ff97dff0092ff00f6c78360fa57bcfc28bdf3fc1b15ab7deb677c71fc2ccc47ebbab7ecfb574167d4572478b1e1e6a4a95ffedeff00807cee71c2f4f17859d09d4d5dacedb34d6b6beba5d6eb7bf93868ae96d3eed1375aaff8885adbeadff93fff006a7c056e06f65ff3117ffb77ff00b63cd7c5bff2e7ff0003ff00d96b9aaf5e9ab3e6ef5ea61fc49e48287d57ff0027ff00ed0fb7c9388bfb072da780f67cfc97d6fcb7bc9cb6b3ef6dcf31accf107fc80ee7fe03ff00a10af5298d507fbd5df47c48f7d4beadb7f7ff00fb433cc3c49e7a13a3f55f89357e7eeadfc8781d15efcb532d7d1d0f11bdaffcc35bfedfff00ed0f837c4b6ff975ff00937fc03e7bc57d8b9ae112a64af9ee2c8ffad7ec3fe5d7b3e6fef5f9b97fc36b72f9ee6b438c7d85ff00737bff007bff00b53b5a2b8b9bfd5d61de7435f374bc36e7ff0098affc93ff00b73b6971b7b47fc0ff00c9bffb535fc41ff21cb9ff0080ff00e822b32b9bbcef58179d0d7d161fc37bc547eb3b7f73ff00b73c1a986fadd79d6bdb99b76ded777f23d0eb9af16ffcb9ff00c0ff00f65ae06f3bd67c7f78d7a987f0c7924aa7d6b6fee7ff006e7d370c617fb2f32a58fbf3725f4daf78b8efaf7bec7534562c3daafc3dabaa5c03cbff00311ff927ff006c7ecb438a3daffcbab7fdbdff0000f41f85566d3f8dedee39096e8e723a64a3003f224fe15efb5f29435a10f6ada9f0a2a10e5556ff00f6effc13cfc6e5f5335c4baf3a9cab4495af656daf757d6ef65bdba1f4ed15f33b542f5cb5f27f65f6eff2ff008242e13bff00cbeffc97ff00b633ad754d3d7ef5fda8faccbfe35b76bae690b8ddaa590fadc27f8d78a515f1d532884f79339eaf14d6a9bd35f7b3e87b4f12684b8ddad69c3eb749fe35d1586bda3c88244d5ac590f46170841fc735f2b5773e1fff00901db7fc0bff004235e4e3720a7087373b36ca6abcd7112a33f76cafa7aa5fa9f49e9dab69b73958350b594a8c90932b63eb83534d7501e93467fe042bc1b4ad4a5d2b508eea2e76f0c99c075ee0ff009eb8af52b1bd8351b38eeadd898dc77ea0f707de8cb7847098c4ef55a92e965b773e438e658ec86a4670a6a7467b4bb4bac5fe6bbebd99ab757d690e3cdba863ddd37c8067f3acc9752b039c5edb1ffb6abfe3581e2dff00973ff81ffecb5cd57d461bc39c2ce0a7ede5f723ab20e1ea79ee574b1f566e2e7cda25a2b49c7afa1dbcb7d667a5d407feda0aa13ea165129924bbb7441d59a4000fc735cbd667883fe40773ff0001ff00d08577d1f0eb0bcea3ede5abec8798787385850a95bdbcaf14decba2b9daaebba3ff00d056c7ff000213fc6a65d7f47ffa0b587fe04a7f8d785d15f4747c3bc352dab4bee47e7cf87293fb6cf795f1068a3fe62f61ff008129fe35bcb6775ff3ed37fdf06be68afb16be538d25feaafd5fd87ef3da736fa5b9797b7f88df0dc2142bdef51ab7923919acee8c7816d31ff801ac7bbd32fd81db65727e9137f857a3515f234fc46c5436a11fbd9dd4f82e8537a557f723c5efac6ed1ca3db4cae3aa94208fc2b02eb4fbd607167707e919ff000af4ef107fc872e7fe03ff00a08accafa2c3f88b8a5052f611d7cd9f2f5b10f09889d18aba8b6bee763ca6eb48d4db3b74ebb3f481bfc2b31b4cbfb761e7d95cc5bb38df132e7f315ed35cd78b7fe5cffe07ff00b2d7a987f12f1539a87b08ebe6cfa7e14c4bcd334a5819ae553e6d56fa45cbf43848ade61d6371f8569d9585e5ce7ecf693cdb71bbcb8cb633eb8ab36b6d35e5cc76f021796438503fcf4af53d274b8748b05b5889639dcee7f89bb9f6e95ef65fc578ac6c9de92515d6efee3ed78bb1984e17a3050973d69ed17a69d5bb74e8bbbf4679d5b689ab4b2048f4bbd773d156ddc93fa56ac5e1bd7475d17511ff006eaffe15ea3e1fff0090e5b7fc0bff004135dcd698bcfaa539f2a8239387b8cabe330eeb4a925676ddf64ff53e7a6f0e6b9ff405d47ff015ff00c2a26f0debbff404d4bff015ff00c2be8aa2bcaad9bceaef147d1ae2bacbfe5dafbd9f00514515e41f2815dcf87ffe4076dff02ffd08d14570e61fc25ebfe67d57087fbf4ffc0ff389a75b9e17d5a7d3f548add70f05cc8b1ba1ec49c061ee33f8fe4414579d85a92a75a3283b3b9f619f612862f2caf47111528b8bd1f92ba7ea9ea9f4674de2dff973ff0081ff00ecb5cd51457ec383fe0c7faea7c87879ff0024de1bfedfff00d3920accf107fc80ee7fe03ffa10a28aeda1fc58faa3e9b34ff71adfe097e4ce1a8a28afa23f1b0afb168a2bf0ef19ff00e607fee27fee33d3cbbed7cbf50a28a2bf0d3d3386f107fc872e7fe03ffa08acca28afa2a1fc28fa23f1bcd3fdfab7f8e5f9b0ae6bc5bff2e7ff0003ff00d968a2bbb07fc68ff5d0fa6f0f3fe4a4c37fdbff00fa6e474be17d1edf4fd362b95f9ee2e23576723a0201da3dbf9fe58dda28afd83094e34e84630565647c8e7f8caf8cccebd6c449ca5ccd5df64ec97a25b234fc3fff0021cb6ff817fe826bb9a28af3f30fe2af4ff33ebf843fdc67fe37f94428a28ae13ea8ffd90000001800530065007100750065006e0063006500540079007000650000000200000000010000001a00530065007100750065006e00630065005300650074007500700000000c000000015a000000000000000300000000000000000000000000000000000000000000005c000000000000000500000000000007800000000000000438000000000000000000000000000000000000803f00000000000000180000000000000000ffffffffffffffff0000000000000000fffffffffffffffe000000000000000100000000000000c40000000000000005000000000000002b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000007800000000000000438000000000000000000000000000000000000803fffffffffffffffff00000000000000010000000000000221000000000000000000000000000000000000000000000000000000000000000000000000000000000000003f0000003f000000000000000a1800200ee802028003040000000c0053006500710052006500660000000a000000004800330035006300370034006300340061002d0063003300610030002d0034006100370038002d0039003600310064002d003900350039003400640066003600380034003600300038000000300049007300520065006e006400650072004300610063006800650042004100560065007200730069006f006e006500640000000100010000002400460069007200730074004d00610069006e0042007500730046006f0072006d00610074000000020000000002</FieldsBlob>
|
|
33
33
|
<UniqueSequenceId>b84501d8-e67f-48a9-a6a2-dba874d6d8fd</UniqueSequenceId>
|
|
34
34
|
<MediaExtents>00000000000000000000000000003040</MediaExtents>
|
|
35
35
|
<FrameRate>00000000000038400000000000000000</FrameRate>
|
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.134.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()}")
|